Posted at

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

More than 3 years have passed since last update.


はじめに

mono は CLI(共通言語基盤)のオープンソース実装です。

CLI は Microsoft が作った仕様で、本家本元の実装が CLR で .NET framework の基盤になっています。

。。。であってると思いますが、ご興味のある方は調べてください。

要は、mono をインストールすると Linux でも C# とか F# とかのコンパイルができます。

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

$ sudo apt-get install mono-dmcs fsharp

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


hello.cs

cusing System;

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


コンパイル。

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

hello.exe ができあがるのですが、このファイルは PE32 形式です。

つまり、このファイルは Windows の .NET 環境で実行ファイルです。

$ file hello.exe 

hello.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

Linux ネイティブの実行ファイルと比較しましょう。

Linux の実行ファイルは ELF 形式です。

$ 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 コマンドに渡すと実行できます。

$ cli hello.exe 

hello, world


mono バイナリを実行する

ここから、本題です。

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

$ ./hello.exe

hello, world

このことを実現しているのが、カーネルモジュール binfmt_misc です。

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

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

$ 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 スクリプトは以下のように実行します。

$ bash myscript.sh

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

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

#!/bin/bash

$ chmod +x myscript.sh      # 実行権を付与する

$ ./myscript.sh # bash に渡さなくても実行できる


ARM バイナリを実行する

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

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

$ suto apt-get install g++-arm-linux-gnueabihf \

qemu-user \
qemu-user-static

hello.c を作ります。


hello.c

#include <stdio.h>

#include <stdlib.h>

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

return EXIT_SUCCESS;
}


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

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

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

$ 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 が設定している)

$ 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 リンクで作ったバイナリも実行できます。

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

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

それは、以下のようにして対処できます。

# /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 画像ファイルを「実行」する設定をしてみましょう。

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

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

$ sudo update-binfmts --display jpeg

jpeg (enabled):
package = <local>
type = magic
offset = 6
magic = JFIF
mask =
interpreter = /usr/bin/eog
detector =

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

$ chmod +x hoge.jpg       # 実行権を付与する

$ ./hoge.jpg # JPEG 画像を「実行」する

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


大雑把に言って、binfmt_misc の設定は、ファイルが該当するかの判断条件と該当した時に渡すインタプリタの指定です。

上の例では、判断条件としてマジックコード(--magic "JFIF" --offset 6)を指定しましたが、他のタイプでも条件設定はできます。

拡張子でやってみましょう。

$ 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 画像ファイルが実行できます。

$ chmod +x hoge.jpg       # 実行権を付与する

$ ./hoge.jpg # JPEG 画像を「実行」する

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


おわりに

動作確認は以下の環境で行っています。


  • Ubuntu Linux 14.04