クロスコンパイル環境構築入門
組み込みLinuxの開発では、プログラムを動かす機器で直接ビルドするかわりに、PC上でコンパイルする「クロスコンパイル」がよく用いられます。しかし、適切にクロスコンパイル環境を構築しないと、ビルドは成功しても実行できない、ライブラリが見つからないなどの問題が生じがちです。
この記事では、クロスコンパイル環境の基本的な構成とツールチェーン・sysrootの役割を解説します。さらに、Yocto(組み込みLinux向けビルドフレームワーク、Yocto Project)が生成するSDKやBuildroot(オープンソースのビルドシステム)で生成したツールチェーンの導入・利用方法、実務で生じやすいトラブルの切り分け方法についても解説します。
クロスコンパイル環境の基本構成
クロスコンパイル環境を理解するには、まずクロスコンパイルの仕組みと、その仕組みを支えるツールチェーンの構成を把握することが重要です。
クロスコンパイルとは何か
ここでは、開発に使用するPCを「ホスト」、プログラムを実行する機器を「ターゲット」、ターゲット上で動作するLinuxなどのOSを「ターゲットOS」、ターゲットOSのルートファイルシステムを「ターゲットrootfs」と呼びます。クロスコンパイルとは、ビルドするホスト環境とは異なるアーキテクチャ向けの実行ファイルを生成する仕組みです。たとえば、x86系のPC上で、ARM 32ビットLinux向けのアプリケーションをビルドする場合がクロスコンパイルに当たります。このクロスコンパイルに対し、ターゲットと同じCPU上でビルドする方法をネイティブコンパイルと呼びます。
組み込みLinuxでは、ホスト上でクロスコンパイルする構成が一般的です。この環境では、コンパイラだけでなく、ターゲット用のヘッダやライブラリを管理するsysrootも必要となります。また、YoctoやBuildrootは、ターゲット向けのツールチェーンやライブラリなどを含むクロスコンパイル環境を構築する目的で使われます。
ツールチェーンの構成要素
ツールチェーンは、C/C++のコンパイラ(gcc/g++)、リンカ(ld)、アセンブラ(as)などの変換ツール群(binutils)に加え、ターゲットのCライブラリ(例:glibc、musl、uClibc-ng)を含む一式です。デバッグ用にgdbなどをあわせて用意することも一般的です。
Cライブラリの種類とバージョンは実行時の動的リンクに直結するため、ターゲット環境にあわせて選択する必要があります。ソースコードは前処理、コンパイル、アセンブル、リンクの順に処理され、最終的な実行ファイルが生成されます。
sysrootの役割とライブラリ管理
クロスコンパイルでは、ホストではなくターゲット用のライブラリやヘッダを参照する必要があります。その中心となるのがsysrootです。sysrootは開発(ビルド/リンク)用に必要なヘッダ・ライブラリ群を集約したもので、実機に載せるrootfsとは一致しない場合があります(Buildrootではstagingがその役割を担います)。
sysrootとは何か
sysrootとは、ターゲットOSで利用するヘッダファイルやライブラリなどを配置したディレクトリです。コンパイラは「--sysrootオプション」などを利用して、このディレクトリを検索対象として参照します。なお、GCCの--sysrootは標準ではヘッダとライブラリの両方の探索に適用されます。-isysrootを併用するとヘッダ探索のみを別途指定できます。ただしDarwin(macOS)向けでは、-isysrootはヘッダとライブラリの両方に適用されます。ツールチェーンの設計にあわせて指定方法を統一します。
この指定によりホストのライブラリを誤って参照するリスクを軽減し、ターゲットOSと整合性の取れたビルドがしやすくなります。Yocto SDKやBuildrootで構築したクロスコンパイル環境では、ターゲットに対応したツールチェーンとsysrootを組み合わせて利用するのが欠かせません。
ライブラリやヘッダの不一致で生じる問題
sysrootが正しく設定されていないと、ホストのヘッダやライブラリを参照してしまい、さまざまな問題が発生するおそれがあります。たとえば、glibcなどのバージョンやABI(Application Binary Interface)がターゲットOSと合っていないと、実行時に必要なシンボルや共有ライブラリが見つからない、といった不具合が生じやすいでしょう。
また、コンパイルは成功してもリンクエラーが発生したり、ターゲット上で共有ライブラリを読み込めず起動できなかったりするケースもあります。クロスコンパイルでは、コンパイル結果だけでなく、利用しているライブラリの整合性も確認することが基本です。
クロスコンパイル環境の構築手順
実務では、SDKの導入からビルド設定までを一貫して整備することで、再現性の高い開発環境を構築できます。
SDK・ツールチェーンの導入
SoC(System on Chip)ベンダーなどが提供するSDKには、クロスコンパイラやsysrootが含まれていることがあります。このSDKを利用すれば、環境構築の手間を削減できる点が強みです。
Yocto Projectでは、ターゲット用sysrootや環境設定スクリプトを含むSDKを生成して配布できます。Buildrootでは、生成したクロスツールチェーンと、そのsysrootに相当するstagingディレクトリを利用します。SDKのバージョン、取得元、環境設定スクリプト、ビルド設定をプロジェクト単位で管理します。必要に応じてコンテナイメージも固定すると、開発メンバー間およびCI環境で同じビルド環境を再現しやすくなります。
環境変数とビルド設定
クロスコンパイルでは、PATHへツールチェーンの実行ファイルを追加し、CROSS_COMPILEでクロスコンパイラなどのツール名のプレフィックスを指定することが一般的です。また、sysrootの指定により、ターゲット用のヘッダやライブラリを参照します。
さらに、pkg-configを利用する場合は、SDKが提供する環境設定スクリプトまたはクロスコンパイル用ラッパーを使用します。ラッパーを使用しない場合はPKG_CONFIG_SYSROOT_DIRとPKG_CONFIG_LIBDIRを設定し、ターゲットのsysroot内にある.pcファイルだけを参照するとよいでしょう。環境変数の設定ミスは、原因の特定が難しい不具合につながるため、設定内容を共有する仕組みも重要です。
CMake/Makefileでの利用方法
CMakeでは、ツールチェーンファイルにコンパイラやCMAKE_SYSROOTなどを記述することで、クロスコンパイル環境を統一できます。プロジェクト全体で同じ設定を利用できるため、コンパイルの再現性が高まります。
Makefileでは、CCやCXX、SYSROOTなどの変数を定義して管理し、CROSS_COMPILEを利用してコンパイラを切り換えるアプローチが効果的です。また、pkg-configと組み合わせることで、ライブラリ情報を自動取得でき、ビルド設定の保守性も向上します。
クロスコンパイル時によくあるトラブル
環境構築が完了しても、実行時に思わぬ問題に遭遇することがあります。原因を切り分けるための確認方法を知っておきましょう。
コンパイルは成功するが動作しない
ビルドが成功しても、ライブラリのバージョン不一致や動的リンクの失敗により、ターゲットで起動できないことがあります。また、誤ったCPUアーキテクチャ向けにビルドしてしまう場合もあるため、注意が必要です。
このような場合は、まずfileでCPUアーキテクチャを確認します。次にreadelf -lでプログラムインタープリタを、readelf -dで共有ライブラリ依存関係を確認します。最後に、ターゲットrootfs内に指定された動的リンカと共有ライブラリが配置されているかを確認します。
実務で確認すべきチェックポイント
トラブル発生時には、前述のfile・readelfに加え、依存関係の確認も有効です。ただし、lddはターゲット上で実行できる場合にのみ使用し、ターゲット側のlddまたは動的リンカの診断機能を用いて依存関係を確認します。ホスト上ではクロス版のreadelf -dを用いてNEEDEDエントリとプログラムインタープリタを確認し、あわせてターゲットrootfs内に必要な共有ライブラリが存在するかを確認します。
また、SDKのバージョンを固定し、コンテナ技術によってビルド環境を統一すると、「開発者ごとに結果が異なる」といった問題を防ぐ上で有効です。さらに、CI/CD(継続的インテグレーション/継続的デリバリー)へクロスビルドを組み込めば、環境の変化による不具合を早期に検出し、安定した開発体制を維持できます。


