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

生成バイナリのアーキを確認する

クロスコンパイルで一番やりがちな失敗は、うっかりネイティブの gcc を呼んでホスト向けのバイナリを作ってしまうことです。これを防ぐため、ビルド後は必ず生成物のアーキテクチャを確認します。手軽なのは file コマンドで、ELFの種類・ビット幅・アーキ名・エンディアン・静的か動的かを一行で教えてくれます。より細かく見たいときは readelf -h でELFヘッダを、objdump -f でファイルヘッダを確認します。Arm向けなら file の出力に ELF 32-bit LSB ... ARM のように表示され、ホスト向けの x86-64 とは明確に区別できます。

前のトピックで、クロスコンパイルでありがちな失敗として「うっかりネイティブの gcc を呼んでしまい、ホスト向けのバイナリを作ってしまう」ことに触れました。これは思いのほか起こりやすいミスです。Makefile の中で CROSS_COMPILE の指定が抜けていたり、環境変数の設定を忘れていたり、コピペしたコマンドが素の gcc のままだったり——原因はさまざまですが、結果はどれも同じで、ターゲットで動かないバイナリができあがります。しかもビルド自体はエラーなく完了するので、転送してターゲットで実行する段になってはじめて気づく、ということになりがちです。これを防ぐ最も確実な方法は、ビルドした直後に、生成物のアーキテクチャを必ず自分の目で確認することです。

file コマンドで素早く確かめる

いちばん手軽で、まず最初に使うべきなのが file コマンドです。file はファイルの種類を判別して教えてくれる汎用のツールで、実行ファイル(ELF形式)を渡すと、その素性を一行に要約して返してくれます。たとえばArm向けにビルドした hello に対して file hello と打つと、おおよそ次のような出力が得られます。ELF 32-bit LSB executable, ARM, EABI5 ... dynamically linked。この一行には、ELF形式であること、32bitであること、LSB すなわちリトルエンディアン(バイトの並び順)であること、CPUがARMであること、EABIの版、そして動的リンクか静的リンクかまでが凝縮されています。対してホスト向けのバイナリに file を使うと ELF 64-bit LSB executable, x86-64 ... のように表示され、ARM と x86-64 の違いが一目瞭然です。この一語を見るだけで、狙ったターゲット向けに作れているかが即座に判定できます。

file の出力は読みどころが多いので、要素ごとに意味を押さえておきましょう。「32-bit / 64-bit」はビット幅で、32bit Armなら 32-bit、aarch64 なら 64-bit になります。「LSB / MSB」はエンディアン(endian)で、LSB はリトルエンディアン(最下位バイトが先)、MSB はビッグエンディアンを意味します。Armや x86 系は通常リトルエンディアンなので LSB です。続くアーキテクチャ名(ARM、aarch64、x86-64 など)が、まさにターゲットのCPUを表す核心部分です。そして末尾の「dynamically linked / statically linked」で、前のトピックで触れた動的リンクか静的リンクかが分かります。-static でビルドしたなら statically linked と出るはずで、ここで自分の意図どおりにリンクされたかも確認できます。

readelf でELFヘッダを精密に読む

file の一行で足りないとき、もっと正確で詳しい情報が欲しいときは、readelf を使います。これは binutils に含まれる、ELFファイルの内部構造を読み出す専用ツールです。readelf -h hello とすると、ELFヘッダ(ファイル先頭のメタ情報)が項目ごとに表示されます。-h は header の意味です。出力の中で特に注目すべきは Machine の行で、ここにターゲットアーキテクチャが明記されます。Arm向けなら Machine: ARM、aarch64 向けなら Machine: AArch64、ホスト向けなら Machine: Advanced Micro Devices X86-64 のように表示され、file よりも公式的な形でアーキを確認できます。

readelf の出力では、Machine 以外にも有用な行があります。Class は 32bit か 64bit か(ELF32 / ELF64)、Data はエンディアン(2's complement, little endian など)を示します。Type はファイルの種類で、実行ファイルなら EXEC または DYN(位置独立実行ファイルの場合)、共有ライブラリやオブジェクトファイルなら別の値になります。さらに、クロスツールチェーンの構成によっては、ABIに関する補足情報がフラグとして現れることもあります。file が「ぱっと見の確認」だとすれば、readelf は「内部を開けての精密検査」にあたり、なぜか動かないバイナリの原因を細かく追うときに頼りになります。クロスコンパイラ版の arm-linux-gnueabihf-readelf も同様に使えますが、ホストの readelf でもアーキの異なるELFを問題なく読めます。

objdump と、確認を習慣にする意味

もう一つ、binutils の objdump も確認に使えます。objdump -f hello とすると、ファイルヘッダの概要(architecture という行にアーキ名が出ます)が表示され、file や readelf とは別の角度から素性を確かめられます。objdump はさらに -d で逆アセンブル(機械語を人間が読めるアセンブリに戻す)もできるため、生成された命令そのものを覗いて、本当にArmの命令列になっているかまで踏み込んで確認することも可能です。ふだんはそこまで必要ありませんが、ツールチェーンの挙動を深く調べたいときの引き出しとして知っておくと役立ちます。

肝心なのは、これらの確認を「面倒だから省く」のではなく、ビルド作業の一部として習慣化することです。クロスコンパイルでは、ホスト向けに作ってしまったバイナリは、ターゲットに転送して実行するまで間違いに気づけません。転送・実行という手間をかけた後で「アーキが違った」と判明するのは、時間の無駄であり、もどかしいものです。ビルドが終わったら反射的に file を打つ——たったこれだけのひと手間で、その種の手戻りはほぼ完全に防げます。「作ったら file で確認、怪しければ readelf で精査」を体に染み込ませておくことが、クロスコンパイルを安定して回すための、小さくて確実なコツです。

この項目に出てくる用語

エンディアンえんでぃあん
メモリ上のバイト並び順。リトル/ビッグの2種。
ABIえーびーあい
関数呼び出し規約やデータ配置の約束事(Application Binary Interface)。
トリプレットとりぷれっと
ターゲットを表す識別子。例 arm-linux-gnueabihf。

関連コマンド

filereadelfobjdump

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