maloader - a mach-o loader for linux
https://github.com/shinh/maloader
Linux で動く Mach-O loader を書いています。何ができるかというと、 Mac のバイナリが Linux でそれなりに動きます。今のところ、 gcc 、 otool 、 nm などの、いくつかのコマンドラインツールと、その gcc が生成した簡単なプログラムなんかが動きます。 Safari とか iTunes とかはもちろん動きませんし、動くようにしたいとも思ってないです。
仕組みとしては、引数に与えられた Mach-O バイナリを適当に読み込んで、指定された通りに mmap して、未定義なシンボルは linux の世界から探してきて再配置して、でエントリポイントに jmp する、と。まぁローダの説明そのまんまという感じです。
ローダはまぁ Mach-O のフォーマットが mach-o/loader.h からだけだとわかりにくかったという問題があったんですが、 dyld のコードとかを参考にするとだいたいわかる感じでした。 Mach-O は strip したバイナリを小さくしたいんだろうなぁという工夫がある感じで感心しました。
それと、実行したシンボル名がわかると、再配置したシンボルの中でクラッシュしてるとかがわかって嬉しいとかがあったので、トランポリンみたいなのを作って関数呼び出し前に関数名を表示できる機能をつけたりとかもしました。
意外と苦労したのがシンボルの解決でした。 printf が未定義ですよーとか言われると linux 側のシンボルを適当に探してきてそれを使う感じなんですが、まず存在しないシンボルが結構な数あります。まぁ存在しないぶんには似たようなもんを実装するなり、 apple の libc からコピってくれば良い感じでした。
どっちかというと問題は存在するけどやることが違う関数で、例えば open の引数の O_ASYNC に割り当たってる数字が linux と mac が違う…とかそういうものや、 dirent のようにユーザコードが構造体の中身を触る感じの API になっていて、その構造体のレイアウトが linux と mac で全然違う、なんてものがありました。
特にめんどくさかったのが FILE で、 FILE って普通は中身をユーザコードの中でいじることは無いはずなんですが、 stdio.h の中には結構マクロで定義されてる関数もあって、中身直接いじることもある感じぽかったのでした。しょうがないから mac ぽいレイアウト + 末尾に linux の FILE を持った FILE のラッパみたいな構造体を作ってやって、 FILE をやりとりする API では全部そのラップされた構造体を適当にどうこうするみたいなそういう感じになって、 FILE 使ってる API って結構あることから面倒な感じでした。ていうかまだ対応してない API が結構あるはず。
TODO としては色々ありますが、とりあえず Mac の dyld を読む機能はあってもいいと思ってます。あと、今のところ x86-64 べったりですが、 i686 も暇な時にやってみてもいいかなぁと思ってます。
それと最初は Mach-O => ELF のコンバータだったところを libc の初期化とかがめんどくさいということで今の形になっているのですが、この作りだと C++ 例外とかデバッグ情報とかどうしようもないので、コンバータの方をもうちょい頑張るというのもやってみてもいいかなぁと思っています。