トリプレットとABI
トリプレットは、ターゲットの素性を表す識別子で、おおむね「アーキテクチャ-ベンダ-OS-ABI」の形をとります。たとえば arm-linux-gnueabihf は、Armアーキテクチャ・Linux・glibc系のEABIでハードウェア浮動小数点(hf=hard float)を使う構成を指します。64bit Armなら aarch64-linux-gnu となります。末尾のABI部分は関数の呼び出し規約やデータの並びを決める約束事で、ここがホストとずれていると、たとえコンパイルが通ってもターゲットで正しく動きません。クロスツールの名前を見れば、その道具がどのターゲット用かを読み取れます。
クロスツールの名前に付いていた arm-linux-gnueabihf という文字列。これはトリプレット(triplet)と呼ばれる識別子で、そのツールがどんなターゲット向けなのかを一目で表すための、いわばターゲットの「素性書き」です。triplet は本来「3つ組」という意味で、もともとは3つの要素で構成されていたことに由来しますが、現代ではABIの情報まで含めて実質4つの要素を持つことが多くなっています。クロスコンパイルでは、このトリプレットを正しく読めること、そして自分のターゲットに合ったトリプレットを選べることが、地味ながら決定的に重要です。名前を読み違えると、見当違いのCPU向けにビルドしてしまうからです。
トリプレットの構造を分解する
トリプレットは、おおむね「アーキテクチャ - ベンダ - OS - ABI」という並びになっています。arm-linux-gnueabihf を例に分解してみましょう。先頭の arm がアーキテクチャ、つまりターゲットのCPUの種類で、ここでは32bitのArmを指します。次の linux がOS、すなわちその上で動くオペレーティングシステムを表します(組込みLinuxなので linux です)。最後の gnueabihf がABIの部分で、これが少し情報量が多く、gnu はglibc系の環境であること、eabi はArm用の関数呼び出し規約(Embedded ABI)、末尾の hf は hard float すなわちハードウェアの浮動小数点演算ユニット(FPU)を使う構成であることを示しています。一見すると呪文のような文字列ですが、こうして区切って読めば、ターゲットの姿がくっきり見えてきます。
ベンダ部分について補足すると、4要素の正式なトリプレットでは aarch64-unknown-linux-gnu のように、アーキとOSの間にベンダ名(unknown や pc など)が入ります。ただし実務で使うツール名では、このベンダ部分が省略されて arm-linux-gnueabihf のように3つに見える形が広く使われます。省略されていても意味は変わりません。なお名前に triplet(3つ組)とあるのに要素が4つあるのはこの歴史的経緯のためで、最初はアーキ・OS・ABIの3つだった枠にベンダが加わり、呼び名だけが「トリプレット」のまま残っているのです。64bit Armを例にとると、トリプレットは aarch64-linux-gnu となり、aarch64 が64bit Armアーキテクチャ、linux がOS、gnu がglibc系のABIを表します。x86_64-linux-gnu と並べてみれば、先頭のアーキ名だけが入れ替わっているのが見て取れ、ホストとターゲットの違いがこの一語に集約されていることが分かります。同じArmでも、32bit版と64bit版ではアーキ名が arm と aarch64 で明確に分かれる点は、ボードを選ぶときに取り違えやすいので意識しておきましょう。
ABIがなぜ決定的に重要なのか
トリプレットの中でも、末尾のABI(Application Binary Interface、アプリケーションバイナリインタフェース)は特に注意して理解する価値があります。ABIとは、コンパイル済みのバイナリ同士がやりとりするための、目に見えない約束事の総体です。具体的には、関数を呼ぶときに引数をどのレジスタやスタックに置くか(呼び出し規約)、構造体のメンバをメモリ上にどう並べるか、データ型が何バイトでどう揃えられるか、といった取り決めを含みます。ソースコードのレベルでは見えませんが、機械語のレベルでは厳格に守らねばならないルールです。
ここが食い違うと何が起きるか。たとえば hard float(hf)でビルドしたバイナリと、soft float(浮動小数点をソフトウェアで処理する構成)のライブラリを混ぜると、関数に渡す浮動小数点の値の置き場所が両者で食い違い、計算結果がめちゃくちゃになったり、その場でクラッシュしたりします。やっかいなのは、こうした不整合はコンパイルやリンクの段階では気づかれにくく、ターゲットで実際に動かしてはじめて発覚することが多い点です。「コンパイルは通ったのにターゲットで正しく動かない」というクロスコンパイルの典型的なつまずきの、かなりの部分がこのABIの不一致に由来します。だからこそ、ターゲットのABI(hf なのか、glibc なのか musl なのか等)を正確に把握し、それに一致するトリプレットのツールチェーンを選ぶことが欠かせません。
トリプレットを読めることの実用的な意味
結局のところ、トリプレットを読む力は、クロスコンパイルにおける「ラベル読解力」です。手元のツールチェーンが arm-linux-gnueabihf- で始まっていれば、それは32bit Arm・Linux・glibc・hard float 向けの道具だと即座に判断でき、自分のArmボードがその構成なら安心して使えます。もし aarch64-linux-gnu- だったなら、それは64bit Arm用なので、32bitのボードに使えば動きません。同じく、末尾が gnueabi(hf が付かない)であれば soft float 系であり、hard float のボード向けの構成とは別物です。生成したバイナリが想定どおりのターゲット向けかは、次のトピックで扱う file や readelf でも確認できますが、その前段として、ツール名のトリプレットを読んで「正しい道具を選ぶ」ことが第一の関門になります。ターゲットのデータシートやベンダ提供のSDKには、対応するトリプレットが必ず明記されているので、まずそれを確認し、同じトリプレットのツールチェーンを用意する——これが王道の進め方です。アーキ・OS・ABI——この3点を名前から読み取る習慣が、クロスコンパイルの事故を未然に防ぎます。