ABIとは? プログラム間のバイナリ契約
ABI(Application Binary Interface)は、コンパイルされたコードが相互運用できるようにする低レベルの契約です。関数がどう呼び出され、データがどう配置され、バイナリがどう組み合わさるかを定めます。
APIは、ソースコードが言語レベルでどうインターフェースするかを定義します。ABIは同じ考え方を1つ下の層、機械語のレベルで扱います。2つのコンパイル済みコンポーネントが協働するために合意しなければならないバイナリの詳細、つまり呼び出し規約、引数の渡し方、構造体のメモリ上の配置、シンボルの命名方法を規定します。ABIを壊すと、問題なくコンパイルされたバイナリが実行時に失敗します。
ABIが規定するもの
- 呼び出し規約: 引数と戻り値がどう渡されるか。
- データレイアウト: 構造体のサイズ、アライメント、順序。
- name mangling: シンボル名がobject fileでどうエンコードされるか。
- バイナリ形式と、プログラムがどうロードされるか。
ABI vs API
APIはソースレベルの契約です。どの関数や型が存在し、コード内でどう呼び出すかです。ABIはバイナリレベルの契約です。そのコンパイル済みコードが実際にどう相互運用するかです。APIを安定に保ちながらABIを壊すことも、その逆も可能です。
ABIの安定性が重要な理由
動的リンクはABIの安定性に依存します。共有ライブラリは、新しいバージョンが同じABIを保つ場合にのみ、呼び出し元を再コンパイルせずに更新できます。structのレイアウトや呼び出し規約を変えると、既存のバイナリが分かりにくくデバッグの難しい形で壊れます。
ABIが壊れるとき
ABIの破壊は、ソースには何もおかしく見えないのに起こる、不可解なクラッシュや破損を引き起こします。不一致は純粋にバイナリのものだからです。だからこそ、異なるコンパイラ、フラグ、ライブラリバージョンでbuildされたコンポーネントを混在させると、予測不能に失敗することがあるのです。
簡単な例
共有ライブラリを更新してstructにフィールドが1つ増えると、そのメモリレイアウトが変わります。古いレイアウトに対してコンパイルされたプログラムは依然として古いサイズを期待し、問題なくコンパイルされるソースであっても誤ったバイトを読み取ります。
CIにおけるABI
ABIの不一致は環境の問題です。バイナリが互換性のないツールチェーンやライブラリバージョンに対してbuildされると、同じコードが失敗します。Latchkeyは一貫した再現可能なrunner環境を提供するため、コンポーネントが互換性のあるABIに対してbuildおよびリンクされ、不可解な works-on-my-machine のバイナリ障害を回避します。
重要なポイント
- ABIは、コンパイルされたコードが相互運用できるようにするバイナリレベルの契約です。
- APIとは別物です。ABIは機械語に関わり、APIはソースに関わります。
- ABIの破壊は、ソースが問題なくコンパイルされても、不可解な実行時障害を引き起こします。