Linux: binfmt_misc: mono や ARM のバイナリが実行できる仕組み

# はじめに
mono は CLI(共通言語基盤)のオープンソース実装です。
CLI は Microsoft が作った仕様で、本家本元の実装が CLR で .NET framework の基盤になっています。
。。。であってると思いますが、ご興味のある方は調べてください。

要は、mono をインストールすると Linux でも C# とか F# とかのコンパイルができます。
Ubuntu 14.04 で C# と F# のコンパイラをインストールするには、以下のようにします。

```shell-session
$ sudo apt-get install mono-dmcs fsharp
```

インストールできれば、hello.cs とか作ってコンパイルできます。

```csharp:hello.cs
cusing System;

class Hello
{
        public static void Main()
        {
            Console.WriteLine("hello, world");
        }
}
```

コンパイル。

```shell-session
$ dmcs hello.cs      # 私の環境では Warning がでましたが、本題でないので気にしない(^_^;
```

hello.exe ができあがるのですが、このファイルは PE32 形式です。
つまり、このファイルは Windows の .NET 環境で実行ファイルです。

```shell-session
$ file hello.exe 
hello.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
```

Linux ネイティブの実行ファイルと比較しましょう。
Linux の実行ファイルは ELF 形式です。

```shell-session
$ file /bin/ls
/bin/ls: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=64d095bc6589dd4bfbf1c6d62ae985385965461b, stripped
```

hello.exe は cli コマンドに渡すと実行できます。

```shell-session
$ cli hello.exe 
hello, world
```

# mono バイナリを実行する
ここから、本題です。

Windows .NET 環境の実行形式である hello.exe ですが、実は Linux でもそのまま実行できます。

```shell-session
$ ./hello.exe
hello, world
```

このことを実現しているのが、カーネルモジュール binfmt_misc です。
mono-dmcs パッケージ(と一緒にインストールされた依存パッケージ)が binfmt_misc を利用して mono バイナリを実行できる設定を行っています。(どのパッケージかは特定できてません。すみません)

設定は update-binfmts コマンドで見ることができます。cli はこの設定の名前です。

```shell-session
$ update-binfmts --display cli
cli (enabled):
     package = mono-runtime
        type = magic
      offset = 0
       magic = MZ
        mask = 
 interpreter = /usr/bin/cli
    detector = /usr/lib/cli/binfmt-detector-cli
```
---
binfmt_misc は shebang を汎用的にしたような感じのものです。
bash スクリプトは以下のように実行します。

```shell-session
$ bash myscript.sh
```

ですが、1行目に shebang を記述したスクリプトに実行権を与えて実行すると bash に渡さずに実行できます。

(以下が shebang。インタプリタ(ここでは bash)を指定する)

```bash
#!/bin/bash
```
```shell-session
$ chmod +x myscript.sh      # 実行権を付与する
$ ./myscript.sh             # bash に渡さなくても実行できる
```

# ARM バイナリを実行する
ARM Linux のバイナリを実行しましょう。(私の環境は x64 です)

ARM 用 C/C++ コンパイラと qemu (エミュレータ) をインストールします。

```shell-session
$ suto apt-get install g++-arm-linux-gnueabihf \
                       qemu-user               \
                       qemu-user-static
```
 
hello.c を作ります。

```c:hello.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("hello, world\n");

    return EXIT_SUCCESS;
}
```

コンパイルします。(static でリンクします)

```shell-scssion
$ make CC=arm-linux-gnueabihf-gcc CFLAGS=-static hello
```

できた hello は ARM の ELF形式ですが、そのまま実行できます。

```shell-session
$ file hello
hello: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=37bb4dd6d030787ec273d16ec54665a1c3fc9526, not stripped

$ ./hello
hello, world
```

これも、binfmt_misc の設定がされているからです。(qemu-user-static が設定している)

```shell-session
$ update-binfmts --display qemu-arm
qemu-arm (enabled):
     package = qemu-user-static
        type = magic
      offset = 0
       magic = \x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00
        mask = \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff
 interpreter = /usr/bin/qemu-arm-static
    detector = 
```
---
上では、static リンクでコンパイルしましたが、dynamic リンクで作ったバイナリも実行できます。

```shell-session
make CC=arm-linux-gnueabihf-gcc hello   # hello を dynamic リンクでコンパイル
```

ただし、私が使用している Ubuntu では、hello の実行時に ARM のローダやライブラリが見つけられずにエラーになりました。
それは、以下のようにして対処できます。

```shell-session
# /lib に ローダと共有ライブラリディレクトリのシンボリックリンクを張る
$ cd /lib/
$ sudo ln -s /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 .
$ sudo ln -s /usr/arm-linux-gnueabihf/lib arm-linux-gnueabihf
```


# JPEG 画像ファイルを「実行」する
遊びます。
binfmt_misc に JPEG 画像ファイルを「実行」する設定をしてみましょう。

```shell-session
$ sudo update-binfmts --install jpeg /usr/bin/eog --magic "JFIF" --offset 6
```

設定されたか確認します。

```shell-session
$ sudo update-binfmts --display jpeg
jpeg (enabled):
     package = <local>
        type = magic
      offset = 6
       magic = JFIF
        mask = 
 interpreter = /usr/bin/eog
    detector = 
```

これで、JPEG ファイルに実行権を与えて実行すると画像ビューア eog で表示されるようになります。 

```shell-session
$ chmod +x hoge.jpg       # 実行権を付与する
$ ./hoge.jpg              # JPEG 画像を「実行」する
```

何が表示されるかは、あなた次第!

----
大雑把に言って、binfmt_misc の設定は、ファイルが該当するかの判断条件と該当した時に渡すインタプリタの指定です。
上の例では、判断条件としてマジックコード(--magic "JFIF" --offset 6)を指定しましたが、他のタイプでも条件設定はできます。
拡張子でやってみましょう。

```shell-session
$ sudo update-binfmts --remove jpeg /usr/bin/eog                   # 一旦設定を消して、
$ sudo update-binfmts --install jpeg /usr/bin/eog --extension jpg  # 拡張子で設定
$ sudo update-binfmts --display jpeg                               # 確認
jpeg (enabled):
     package = <local>
        type = extension
      offset = 0
       magic = jpg
        mask = 
 interpreter = /usr/bin/eog
    detector = 
```

これでも、JPEG 画像ファイルが実行できます。

```shell-session
$ chmod +x hoge.jpg       # 実行権を付与する
$ ./hoge.jpg              # JPEG 画像を「実行」する
```

何が表示されるかは、(略)


# おわりに
動作確認は以下の環境で行っています。

- Ubuntu Linux 14.04