🐧 Linux 総合学習プラットフォーム
クロスコンパイル ・ 上級

MakeとconfigureでのCROSS_COMPILE

1ファイルなら手でクロスgccを呼べば済みますが、実際のプロジェクトは Makefile や configure 経由でビルドします。カーネルや多くの組込みMakefileでは、CROSS_COMPILE 変数にトリプレットの接頭辞(末尾のハイフン込みで arm-linux-gnueabihf-)を渡す慣習があり、make が $(CROSS_COMPILE)gcc のように展開してクロスツールを呼びます。Autotoolsを使うソフトでは ./configure --host=arm-linux-gnueabihf を指定すると、生成される Makefile がクロスコンパイラを使うよう構成されます。--build がビルドを実行するホスト、--host が成果物を動かすターゲットを表し、この2つが食い違うときがクロスコンパイルだと configure に伝わります。

ここまでは、hello.c のような1ファイルのプログラムを、コマンドラインで直接 arm-linux-gnueabihf-gcc を打ってビルドしてきました。学習にはこれで十分ですが、現実のソフトウェアはソースファイルが何十、何百とあり、依存関係も複雑で、とても手打ちのコマンドでは扱えません。実際のプロジェクトは、Makefile や configure スクリプトといったビルドの仕組みを通じてビルドされます。そこで最後の関門になるのが、「ビルドの仕組みに対して、ネイティブの gcc ではなくクロスコンパイラを使え」とどう伝えるか、です。幸い、これには確立された作法があり、その中心にあるのが CROSS_COMPILE という変数です。

CROSS_COMPILE という慣習

Linuxカーネルをはじめ、多くの組込み向け Makefile には、CROSS_COMPILE という変数を使う共通の慣習があります。これは、使うツールの名前の「接頭辞」を一括で指定するための変数です。Makefile の中では、コンパイラを直接 gcc と書く代わりに $(CROSS_COMPILE)gcc のように書いておきます。こうしておくと、CROSS_COMPILE が空のとき(=何も指定しないとき)は $(CROSS_COMPILE)gcc がただの gcc に展開され、ネイティブビルドになります。一方、CROSS_COMPILE に arm-linux-gnueabihf- を渡すと、$(CROSS_COMPILE)gcc が arm-linux-gnueabihf-gcc に展開され、同じ Makefile がそのままクロスビルドへ切り替わるのです。

ここで一つ、見落としやすい大切な点があります。CROSS_COMPILE に渡すのはトリプレットそのものではなく、末尾のハイフンまで含めた接頭辞だということです。すなわち arm-linux-gnueabihf ではなく、arm-linux-gnueabihf- と書きます。これは $(CROSS_COMPILE)gcc という連結のしかたを考えれば腑に落ちます。接頭辞の末尾にハイフンが無いと、arm-linux-gnueabihfgcc という存在しないツール名になってしまうからです。同じ仕組みで、$(CROSS_COMPILE)ld は arm-linux-gnueabihf-ld に、$(CROSS_COMPILE)objcopy は arm-linux-gnueabihf-objcopy に展開され、コンパイラだけでなくツールチェーン全体が一斉にクロス版へ切り替わります。実際にカーネルをクロスビルドするときは、make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- のように、ターゲットアーキを表す ARCH と組み合わせて指定するのが定番です。

Autotools の configure でのクロス指定

もう一つ、広く使われるビルドの仕組みが GNU Autotools です。OpenSSL や多くのオープンソースソフトは、ビルド前に ./configure というスクリプトを走らせて、環境を調べながら Makefile を自動生成します。この configure に対してクロスコンパイルを伝えるには、--host オプションを使います。たとえば ./configure --host=arm-linux-gnueabihf と指定すると、configure は「成果物はArm向けだ」と理解し、arm-linux-gnueabihf-gcc などのクロスツールを使う Makefile を生成します。configure は賢く、指定された host 名から対応するクロスコンパイラを自動的に探してくれるので、利用者は --host を渡すだけで済むことが多いのです。

--build と --host の違いを理解する

Autotools のクロスコンパイルを正しく理解するうえで欠かせないのが、--build と --host という2つの概念の区別です。--build は「ビルド作業を実行する機械」、すなわちホストを指します。--host は「成果物が動く機械」、すなわちターゲットを指します。紛らわしいことに、Autotools の用語ではこちらの「ターゲット」を host と呼ぶので、ふだんの言葉づかいと逆転していて混乱しがちですが、ここは「host = 成果物が動く先」と割り切って覚えてください。ネイティブコンパイルでは、ビルドする機械と動かす機械が同じなので --build と --host が一致します。これに対して、この2つが食い違っているときこそがクロスコンパイルであり、configure はその食い違いを検知して、自動的にクロスビルド用の設定に切り替えます。つまり ./configure --build=x86_64-linux-gnu --host=arm-linux-gnueabihf という指定は、「x86_64のPC上でビルドして、Arm向けの成果物を作る」という意図を、そのまま configure に伝えていることになります(--build は多くの場合 configure が自動判定するので省略できます)。

なお、Autotools には --target という第3のオプションもありますが、これは少し特殊で、コンパイラやbinutilsそのものをビルドするとき——つまり「このツールが生成するコードのターゲット」を指定したいとき——に使うものです。普通のアプリをクロスコンパイルする場面では --build と --host の2つを押さえておけば十分で、--target を意識する必要はほとんどありません。

仕組みで指定することの意味

CROSS_COMPILE 変数にせよ configure の --host にせよ、根っこにある発想は共通しています。ソース側のビルドの仕組みは「ツールの名前を直接書かず、外から差し替えられるようにしておく」、そして利用者は「どのターゲット向けかを、変数やオプションで外から一度だけ指定する」。この役割分担のおかげで、同じソースコードを、ネイティブでもArm向けでもaarch64向けでも、設定を変えるだけで使い回せます。1ファイルなら手で arm-linux-gnueabihf-gcc を打てば済みますが、規模が大きくなったら CROSS_COMPILE と --host に任せる——この使い分けが、実務でのクロスコンパイルの勘どころです。ビルドが完了したら、これまでのトピックと同じく file や readelf で主要な生成物のアーキテクチャを確認し、狙いどおりターゲット向けに作れているかを最後に検算しておくと、安心して次の工程(ターゲットへの転送と実行)へ進めます。

この項目に出てくる用語

クロスコンパイルくろすこんぱいる
動かす機械(ターゲット)とは別の機械(ホスト)上で実行ファイルを作ること。
トリプレットとりぷれっと
ターゲットを表す識別子。例 arm-linux-gnueabihf。
ホストとターゲットほすととたーげっと
ビルドを行う機械がホスト、成果物を動かす機械がターゲット。

関連コマンド

make./configurearm-linux-gnueabihf-gcc

▶ 学習アプリでこの続きを学ぶ・演習する