MIPSのABIは変だという話をこないだのシステムプログラミング会でしたら、ややザワッとしたので、なにがおかしいのかというのをちょっとまとめてみました。まとめてみて思いましたが、やっぱりMIPSのELFファイルはちょっと変です。
これが僕は一番ひどいと思ったものです。
ファイルにマルチバイトの数値を保存するときはエンディアンというものが問題になります。たとえば0xBEEFという2バイトの数を保存するときは、1バイト目にBE、2バイト目にEFを書くか、逆順で書くかは、ただの決まり事でどっちでもいいわけですが、書く側と読む側で認識があっていないと困ります。世の中的にはリトルエンディアン(下位バイトから書く)のが主流ですがビッグエンディアンなシステムもあります。
それがですね、MIPSのELFヘッダのr_infoという64ビットのフィールドはリトルエンディアンでもビッグエンディアンでもない謎なエンコーディングになっています。具体的には下位32ビットが最初にリトルエンディアンで書かれていて、ビッグエンディアンでエンコードされた上位32ビットがそれに続くという構成になっています。つまりリトルエンディアンだとABCD EFGH、ビッグエンディアンだとHGFE DCBAとなるところが、MIPSのmixed endianだとEFGH DCBAというふうにファイルに書かれていることになります。
このフィールドだけがこの謎のエンコードです。Mixed endianというのがあるのは知ってましたが、そういうのはPDP-11みたいな太古のコンピュータで使われていたエキゾチックなエンコーディングだとばかり思っていました。まさか現代に普通に出会うとは。別に出会いたくはなかった。
これ、なんなんでしょうか。初期の頃にバグって実装したままバグ互換でいままで続けてきたようにしか思えないんですが。
リロケーションは普通は一つ一つ個別に処理できるものですが、MIPSの場合ペアになっているリロケーションというがあります。たとえばHI16リロケーションはLO16リロケーションが直後にあるので、両方の値を足しあわせてリロケーションを適用しないといけない、というようになっています。
これだけでも例外的なので実装が面倒なんですが、実際はGCCやClangの出力するオブジェクトファイルはこのABIさえ守っておらず、HI16とLO16がリロケーションテーブルで泣き別れになって遠く離れて入っていたりします。なのでHI16を見つけたらリロケーションをリニアサーチしないといけない。面倒なだけでなく遅くなるのでやめてほしいです。
どんなUnixでも普通は_start
という名前の関数から実行が始まります(スタティックリンクされる_start
からいろいろ初期化が行われたあとユーザ定義のmain
が呼ばれる)。MIPSでは__start
がエントリポイントになっています。なぜかアンダースコアが一つ多い。
GOTテーブルの使い方に変なルールが加えられていて他のアーキテクチャ向けのELFとは違う作り方をしないといけません。もう具体的にどうなっているのかすら忘れてしまった。僕が忘れるくらいなので相当です。
普通のプロセッサではリトルエンディアンかビッグエンディアンのどちらかが標準ということになっています。x86はそもそもリトルエンディアンしかサポートしていないし、ARMやPowerPCのように両方サポートしているものでも実質はどちらかしか使われていないんですが、MIPSではなぜか両方が広く使われていて両対応しないといけません。まあこれはMIPSの問題というわけではないですが、対応が微妙に面倒。
MIPS専用のパッチが送られてくるたびにこの動作はなんなのか質問するわけですが、ABIを包括的にカバーしているドキュメントがないので何が正しいのか誰も知らないということがあります。こないだなどは、ある動作の最も信用のおけるドキュメントはGNUリンカのソースのコメントということがありました。パッチを書く人になんの罪もないわけですが、ABIみたいな基本的なところでソースが仕様みたいになっているって、どうなんでしょうか。
というわけでMIPSのELFは一応ELFではあるんですが、ところどころ作りがおかしくてMIPSバリアントみたいになってしまっています。MIPSで動くUnixの実行ファイルフォーマットが変という話であって、MIPSというCPUが変というわけではないんですが、MIPSには責任をもって直してほしいですよね。とはいえMIPSのABIのオーナーといえるのはいまは誰なんでしょう? それもいまいち謎。
Rui Ueyama (2016-07-14)