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

sysrootとライブラリ

ターゲット用のプログラムが外部ライブラリ(たとえば libssl や zlib)を使う場合、リンク時にはホスト用ではなくターゲット用のヘッダとライブラリが要ります。これらを一か所に集めたディレクトリツリーが sysroot で、ターゲットの / 以下を切り出したような構造を持ちます。gcc に --sysroot=パス を渡すと、コンパイラはそのツリーの中から #include やライブラリを探します。sysroot を使わずホストの /usr/include を混ぜてしまうと、アーキの違うライブラリをリンクしてリンクエラーや実行時クラッシュの原因になるため、ライブラリ依存があるビルドでは sysroot の指定が要になります。

ここまでに作ってきた hello のような単体のプログラムは、標準Cライブラリくらいしか外部に依存しないので、トリプレット付きの gcc を呼ぶだけで素直にビルドできました。しかし実際のソフトウェアは、もっと多くの外部ライブラリに頼って動きます。たとえば暗号通信なら OpenSSL(libssl)、データ圧縮なら zlib、画像処理なら libpng、といった具合です。こうしたライブラリに依存するプログラムをクロスコンパイルしようとすると、新しい問題が立ちはだかります。リンクのために必要となるヘッダファイルとライブラリ本体が、ホスト用のものではなく、ターゲット用のものでなければならない、という問題です。この問題を解決する仕組みが sysroot(システムルート)です。

なぜホストのライブラリではいけないのか

話の前提を整理しましょう。プログラムがあるライブラリを使うとき、コンパイルには2つの材料が要ります。一つはそのライブラリの使い方を宣言したヘッダファイル(たとえば openssl/ssl.h)、もう一つはライブラリの実体である本体ファイル(libssl.so や libssl.a)です。ネイティブコンパイルなら、これらはホストの /usr/include や /usr/lib にあり、gcc が自動的に見つけてくれます。ところがクロスコンパイルでは、これをそのまま使ってはいけません。なぜなら、ホストの /usr/lib にあるライブラリはホストのアーキテクチャ(x86_64)向けにコンパイルされたものだからです。Arm向けのプログラムに x86_64 のライブラリをリンクしようとすれば、アーキの違うコード同士を結合することになり、リンカがエラーを出すか、運悪く通ってしまってもターゲットで実行時にクラッシュします。

つまり、クロスコンパイルでライブラリを使うには、そのライブラリのターゲット向けにビルドされた版のヘッダと本体を、別途用意しておく必要があります。Armボードで動くプログラムをリンクするなら、Arm向けにコンパイルされた libssl.so と、それに対応する openssl/ssl.h が手元に要る、ということです。この「ターゲット向けのヘッダとライブラリ一式」を、ばらばらに置くのではなく、ある決まった構造でひとまとめにしたディレクトリツリーが sysroot です。

sysrootとは何か

sysroot は、ひとことで言えば「ターゲットの / 以下を切り出してきたような、ミニチュアのファイルツリー」です。ターゲットの実機の中にある /usr/include や /usr/lib、/lib といったディレクトリ構造を、ホスト上のあるディレクトリの中に再現したものだと考えてください。たとえばホスト上に /opt/arm-sysroot というディレクトリを用意し、その中に usr/include/(ターゲット向けの各種ヘッダ)、usr/lib/ や lib/(ターゲット向けの共有ライブラリ)が入っている、という構造です。コンパイラにこのツリーを指し示してやれば、コンパイラはヘッダやライブラリをホストの本物の / からではなく、この sysroot の中から探すようになります。結果として、ターゲット向けの正しいヘッダとライブラリだけを使ってリンクできるわけです。

--sysroot オプションで指定する

gcc に sysroot を教えるには、--sysroot オプションを使います。arm-linux-gnueabihf-gcc --sysroot=/opt/arm-sysroot -o app app.c -lssl -lcrypto のように指定すると、コンパイラは #include で書かれたヘッダを /opt/arm-sysroot/usr/include から探し、-lssl で要求された libssl を /opt/arm-sysroot/usr/lib や /opt/arm-sysroot/lib から探します。これによって、ホストの /usr/include を一切混ぜることなく、ターゲット向けのライブラリだけで一貫してビルドできます。crosstool-NG や Buildroot、Yocto が生成するツールチェーンには、対応する sysroot が最初から同梱されていることが多く、その場合はコンパイラがデフォルトの sysroot を内部に持っているため、--sysroot を明示しなくても適切なツリーを見てくれることもあります。

sysrootを使わないと何が起きるか

sysroot の重要さは、それを使わなかったときの失敗を知るとよく分かります。もし --sysroot を指定せず、かつコンパイラがホストの標準パスを探しに行ってしまうと、Arm向けのビルドなのにホストの /usr/include のヘッダや、x86_64 向けの /usr/lib のライブラリが紛れ込みます。すると、アーキの食い違いによる「互換性のないライブラリ」というリンクエラーが出たり、最悪の場合はリンクが通ってしまってターゲットでの実行時にクラッシュしたりします。前のトピックで触れたABIの不一致と並んで、これはクロスコンパイルでライブラリを扱うときの二大つまずきポイントです。だからこそ、外部ライブラリへの依存があるビルドでは、sysroot を正しく指定すること——あるいは sysroot を内蔵した正しいツールチェーンを使うこと——が要になります。

実務の感覚としては、単体の小さなプログラムを試すうちは sysroot を意識しなくても済みますが、OpenSSL や各種ライブラリに依存する実用的なアプリをクロスビルドし始めた瞬間に、sysroot は避けて通れないテーマになります。「ターゲット向けのライブラリ世界を、ホスト上に一式そろえて、それだけを見てビルドする」——この発想を押さえておけば、ライブラリ依存のあるクロスコンパイルでつまずいたとき、どこを疑えばよいかが見えてきます。なお、生成したバイナリが本当にターゲット向けの共有ライブラリを参照しているかは、readelf -d でダイナミックセクション(必要なライブラリの一覧)を確認して確かめられます。

この項目に出てくる用語

sysrootしすルート
ターゲット用のヘッダとライブラリを集めたディレクトリツリー。
ツールチェーンつーるちぇーん
コンパイラ・アセンブラ・リンカ・ライブラリなどビルドに必要な道具一式。
ABIえーびーあい
関数呼び出し規約やデータ配置の約束事(Application Binary Interface)。

関連コマンド

arm-linux-gnueabihf-gcc./configurereadelf

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