ELF・HEX・BINの違い
ビルドが終わると、出力フォルダには .elf、.hex、.bin といった拡張子のファイルが並びます。基本的に同じ実行コードを、用途に合わせて別形式で表現したものなのに、なぜ複数あるのか。書き込みツール(ライタ/プログラマ)にはどれを渡せばいいのか。本記事では、3つのファイル形式の中身と役割の違い、objcopyによる相互変換、そして用途別の使い分けの定石を解説します。
まず全体像:3形式の役割
| 形式 | 中身 | アドレス情報 | 主な用途 |
|---|---|---|---|
| ELF | コード+シンボル・デバッグ情報・セクション情報 | あり | 開発中のデバッグ、デバッガからの書き込み |
| HEX(テキスト形式) | テキスト形式・データ+アドレス+チェックサム | あり | 書き込みツール(ライタ/プログラマ)での書き込み |
| BIN | メモリイメージそのもの(生データ) | なし | ブートローダ、OTA(Over-the-Air)更新、量産書き込み |
ELFはビルドの最終段階でリンカが生成する実行形式ファイルで、実行コードに加えて関数名・変数名(シンボル)やソース行との対応(デバッグ情報)まで、すべての情報を含んだ形式です。デバッガで変数名を用いてメモリを参照したり、ソース行に対してブレークポイントを設定できたりするのはこの情報のおかげです。HEXとBINは、そこから書き込みに必要なデータ部分を取り出した配布用の形式と考えると整理しやすくなります。なお、この記事でHEXと呼ぶのは、HEX形式と呼ばれるテキスト形式のことです。以降は「HEX」と表記します。
HEXの中身をのぞいてみる
HEXファイルはテキストエディタで開けます。各行が1レコードで、行頭のコロンに続いて「バイト数・アドレス・レコードタイプ・データ・チェックサム」が16進数の文字で並びます。末尾のチェックサムは、バイト数・アドレス・レコードタイプ・データをバイト単位で加算した合計の最下位8ビットに対する2の補数(1バイト)で、転送・保存時のデータ化けを検出できます。アドレスフィールドは16ビットですが、拡張リニアアドレス(レコードタイプ04)で、後続のデータレコードに適用されるアドレスの上位16ビット(ビット16〜31)を指定することで、最大4 GBのアドレス空間に対応します。
つまりHEXは「どのアドレスに何を書くか」を自己記述していて、チェックサムによる検証までできる、多くの書き込みツールで扱いやすい形式です(ただし装置・工程によってはBIN指定のケースもあります)。同じ思想のテキスト形式に、行頭がSで始まるSレコード形式(.mot/.srec)があり、国産マイコンの開発環境ではこちらが標準のことも多いです。なお、1バイトを16進数2文字で表すため、ファイルサイズはBINの2倍以上になりますが、書き込まれる内容は同じです。
BINの特徴と落とし穴
BINはメモリイメージをそのまま並べた生データで、ヘッダもアドレス情報もありません。シンプルであることが最大の特長で、ブートローダが受信してフラッシュメモリに書き込む、OTA更新でダウンロードする、といったプログラム側で扱う用途に向きます。一方で落とし穴も2つあります。
- 書き込み開始アドレスを自分で指定する必要がある:ファイル自身は書き込み先アドレスの情報を保持していません。ツールやブートローダに渡すオフセットを間違えると、正常に書き込めたように見えて起動しません。
- 離れた領域があると間が埋められて巨大化する:たとえば先頭のベクタ領域と、離れたアドレスの設定領域を1つのBINにすると、その間の未使用領域までパディングで埋まり、ファイルが数MB(メガバイト)に膨れ上がることがあります。HEXはアドレス付きなので、使う領域だけを記述できます。
objcopyで相互変換する
GCC環境では、binutilsのobjcopyでELFからHEX/BINを生成するのが定番です。ビルドの後処理(ポストビルド)に組み込んでおきます。
# ELF → HEX
objcopy -O ihex app.elf app.hex
# ELF → BIN
objcopy -O binary app.elf app.bin
# HEX → BIN(既存のHEXしかないとき)
objcopy -I ihex -O binary app.hex app.bin
# BIN → HEX(アドレスを加算して開始アドレスを与える)
objcopy -I binary -O ihex \
--change-addresses 0x08000000 app.bin app.hex
BINからHEXへ変換する場合は、失われているアドレス情報をオプションで与える必要がある点に注意します。なお --change-addresses は絶対アドレスを設定するオプションではなく、既存のアドレスに指定した値を加算するオプションです。BINはアドレス情報を持たないため、変換時は先頭を基準(0相当)として扱われ、結果として --change-addresses への加算で開始アドレスを表現できます。※--adjust-vma は --change-addresses の別名です。特定セクションだけを調整したい場合は --change-section-vma sectionname=incr のようにセクション指定のオプションを使います。また、リンカスクリプトでロードアドレスを指定する方法もあります。ここでは加算系オプションの一例を示しています。
使い分けの定石
- 開発中:ELFをデバッガで書き込み、そのままデバッグ。シンボル情報はmapファイルとあわせてサイズ分析にも使えます(「mapファイルの読み方」参照)。
- 量産・検査ライン:HEXまたはSレコード。アドレス・チェックサム付きで、書き込みツールとの相性がよい形式です。
- ブートローダ・OTA更新:BIN+自前ヘッダ。BINの先頭または別ファイルに、サイズ・バージョン・CRCを持つヘッダを付け、受信側で検証してから書き込みます(「CRC実装時のポイント」参照)。
ありがちなトラブル
- BINを書き込んだのに起動しない → 書き込みオフセットの指定漏れ・誤り。アプリのリンク開始アドレスと書き込み先が一致しているか確認します。
- BINのファイルサイズが極端に大きい → 離れたセクションの間がパディングされています。領域ごとにBINを分けるか、HEXを使います。
- HEXとBINでファイルサイズが異なる場合 → テキスト化による差で正常です。中身の同一性はチェックサムやCRCで確認します。
まとめ
ELFは開発用のすべての情報を含んだ形式、HEXはアドレスと検証情報を持つ配布用テキスト、BINは扱いやすいがアドレス情報を持たない生データ。この3点を押さえれば、「どれを渡すか」で迷うことはなくなります。ブートローダやOTA更新を設計する段階になったら、BIN+ヘッダ方式の設計に進むとよいでしょう。

