前回は、QEMU (ターゲットは STM32F4-Discovery)で動かしたELFファイルの内容を確認しました。
今回は、このELFファイルをバイナリファイルに変換して、デバッグ情報、シンボル情報を削除して、そこから、ELFファイルを再構築していきたいと思います。
それでは、やっていきます。
- 参考文献
- STM32F4 のマニュアル
- はじめに
- STM32CubeProgrammer(STM32CubeProg)をインストール
- STM32CubeProgrammerで作成したファームウェアの内容を確認する
- バイナリファイルからELFファイルを構築する
- おわりに
参考文献
STM32F4 のマニュアル
下記リンクのドキュメント→リファレンスマニュアル、ドキュメント→プログラミングマニュアルなどを参照してください。最近は、マニュアルが日本語化されていて、とても便利です。
デザイン/サポート | STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品
はじめに
「QEMUを動かす」の記事一覧です。良かったら参考にしてください。
・第2回:STM32(ARM Cortex-M)をQEMUで動かす(ソースコード確認編)
・第3回:STM32(ARM Cortex-M)をQEMUで動かす(スタートアップルーチン編)
・第4回:STM32(ARM Cortex-M)をQEMUで動かす(リンカスクリプト編)
・第5回:STM32(ARM Cortex-M)のELFファイルの内容を確認する
・第6回:STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う ← 今回 ・第7回:STM32(ARM Cortex-M)のバイナリから構築したELFファイルをQEMUで動かす
・第8回:QEMUのビルドに必要なxpm(xPack Project Manager)について学ぶ
・第9回:QEMUをソースからビルドして動かす
・第10回:QEMUのソースコードを変更してSTM32の動作を変える
それでは、やっていきます!
STM32CubeProgrammer(STM32CubeProg)をインストール
ELFファイルを STM32F4-Discovery のファームウェア形式に変換するために STM32CubeProgrammer をインストールします。
以下から STM32CubeProgrammer をダウンロードします(ユーザ登録が必要です)。
ダウンロードできたら、解凍します。フォルダに入った状態で圧縮されてないので、フォルダを作った状態で解凍します。解凍できたら、セットアッププログラム(SetupSTM32CubeProgrammer-2.16.0.linux)を起動します。
$ mkdir en.stm32cubeprg-lin-v2-16-0 $ mv en.stm32cubeprg-lin-v2-16-0.zip en.stm32cubeprg-lin-v2-16-0/ $ cd en.stm32cubeprg-lin-v2-16-0/ $ unzip en.stm32cubeprg-lin-v2-16-0.zip $ ./SetupSTM32CubeProgrammer-2.16.0.linux
インストーラの起動画面が表示されるので、Nextをクリックします。

STM32CubeProgrammer の情報が表示されるので、Nextをクリックします。

ライセンス確認の画面が表示されるので、問題なければ、I accept ... にチェックを入れて、Nextをクリックします。

インストール先の指定が表示されるので、必要に応じて変更して、Nextをクリックします。

新しくフォルダを作るというメッセージが表示されるので、OKをクリックします。

利用規約が表示されるので、問題なければチェックを入れて、Nextをクリックします。

インストールするコンポーネントの選択の画面が表示されますが、特に変更しなくていいと思うので、Nextをクリックします。

インストールが完了したら、Nextをクリックします。

ショートカットの作成有無と、インストールするユーザの選択画面が表示されるので、お好みで指定して、Nextをクリックします。

ようやくインストール完了です!

STM32CubeProgrammerで作成したファームウェアの内容を確認する
STM32CubeProgrammerでファームウェアを作成
まず、STM32CubeProgrammer を使って、サンプルソースをビルドして生成されたELFファイルを、STM32F4-Discovery のファームウェアに変換していきます。
STM32CubeProgrammer を起動します。

ソフトウェアの改善のために統計情報を取得することの確認画面が表示されるので、OKをクリックします。

Open File をクリックして、ELFファイルを指定して、開くをクリックします。

ELFファイルの内容が表示されます。右上のDownloadの▼をクリックして、Save as をクリックします。適当なフォルダとファイル名を選択して、保存をクリックします。

これで、ELFファイルをファームウェアに変換できました。
objcopyを使ってELFファイルからバイナリファイルを作成
上では STM32CubeProgrammerを使って、ファームウェアを作成しましたが、クロスコンパイラに付属する objcopy でも、ELFファイルからバイナリファイルが作成できます。
$ arm-none-eabi-objcopy -O binary stm32f4discovery_sample.elf stm32f4discovery_sample_objcopy.bin
2つのバイナリファイルを比較する
それでは、比較して、違いを見てみたいと思います。そもそもサイズが全然違いますね。
$ ll stm32f4discovery_sample*.bin -rw-rw-r-- 1 daisuke daisuke 402781952 5月 13 00:13 stm32f4discovery_sample.bin -rwxrwxr-x 1 daisuke daisuke 11116 5月 13 00:19 stm32f4discovery_sample_objcopy.bin*
WinMergeで比較すると、先頭は全く同じです。ファイル末尾まで見てみると、STM32CubeProgrammer で変換した方は、後ろに 0xFF が埋められているだけでした。
もしかすると、STM32CubeProgrammer で変換した方は、スタックアドレスの 0x20200000 までファイルが作られているのかと思いましたが、402,781,952byte は、16進数で 0x1801F700 なので、RAMが始まる 0x20000000 にも達していませんので違うようです(アドレス情報が無いので前に詰められているのかもしれません)。
どちらにしても、STM32CubeProgrammer は、このような使い方は想定していないということだと思います。

STM32CubeProgrammer で変換したバイナリファイル、もしくは、objcopy で出力されるバイナリファイルは、先頭が、前回確認した TEXTセクションの内容になっています。ヘッダなどは無く、単純にデータを出力するだけということが分かります。
バイナリファイルからELFファイルを構築する
今度は、逆に、バイナリファイルからELFファイルに変換してみたいと思います。
先ほど見たように、バイナリファイルにはアドレス情報がなくなっていますし、デバッグ情報、シンボルなども全て無くなっているので、元通りにすることは出来ません。
ここで行うのは、あくまで、QEMU での実行を可能にするために、最低限のELFファイルとして構成し直すことです。
単純に objcopy でELFファイルを作る
まず、1つ目の方法です。
同じ objcopy を使って逆変換します。
$ arm-none-eabi-objcopy -I binary -O elf32-little --change-section-address .data=0x08000000 stm32f4discovery_sample_objcopy.bin stm32f4discovery_sample_objcopy.elf
ELFファイルとしての情報を確認します。
エントリポイントが 0 になっていることと、プログラムヘッダが作成されていません。
$ readelf -h stm32f4discovery_sample_objcopy.elf ELF ヘッダ: マジック: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 クラス: ELF32 データ: 2 の補数、リトルエンディアン Version: 1 (current) OS/ABI: UNIX - System V ABI バージョン: 0 型: REL (再配置可能ファイル) マシン: なし バージョン: 0x1 エントリポイントアドレス: 0x0 プログラムヘッダ始点: 0 (バイト) セクションヘッダ始点: 11416 (バイト) フラグ: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 5 Section header string table index: 4
bin2elf.shを使う
次の方法は、GitHubで公開されていたスクリプトを使います。ARM用に実装されていて、かなり助かりました。
https://gist.github.com/tangrs/4030336
ソースの内容を説明します。まず、コマンドライン引数の説明です。
- 第1引数:入力バイナリファイルパス
- 第2引数:出力パス
- 第3引数:配置するアドレス
スクリプト内で、リンカスクリプトを作っています。ldコマンドの引数は、-b が入力フォーマット形式で、-r が再配置可能な形式でオブジェクトファイルとして出力するという意味になります。
最後にリンクして、ELFファイルを作っています。
#!/bin/sh # Convert a raw binary image into an ELF file suitable for loading into a disassembler cat > raw$$.ld <<EOF SECTIONS { EOF echo " . = $3;" >> raw$$.ld cat >> raw$$.ld <<EOF .text : { *(.text) } } EOF CROSS_PREFIX=arm-none-eabi- ${CROSS_PREFIX}ld -b binary -r -o raw$$.elf $1 ${CROSS_PREFIX}objcopy --rename-section .data=.text \ --set-section-flags .data=alloc,code,load raw$$.elf ${CROSS_PREFIX}ld raw$$.elf -T raw$$.ld -o $2 ${CROSS_PREFIX}strip -s $2
それでは、このスクリプトで ELFファイルを作ります。
$ ./bin2elf.sh stm32f4discovery_sample_objcopy.bin stm32f4discovery_sample_bin2elf.elf 0x08000000
成功しました。ELFファイルの情報を確認します。
$ readelf -h stm32f4discovery_sample_bin2elf.elf ELF ヘッダ: マジック: 7f 45 4c 46 01 01 01 61 00 00 00 00 00 00 00 00 クラス: ELF32 データ: 2 の補数、リトルエンディアン Version: 1 (current) OS/ABI: ARM ABI バージョン: 0 型: EXEC (実行可能ファイル) マシン: ARM バージョン: 0x1 エントリポイントアドレス: 0x8000000 プログラムヘッダ始点: 52 (バイト) セクションヘッダ始点: 15232 (バイト) フラグ: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 3 Section header string table index: 2 $ readelf -l stm32f4discovery_sample_bin2elf.elf Elf ファイルタイプは EXEC (実行可能ファイル) です エントリポイント 0x8000000 There is 1 program header, starting at offset 52 プログラムヘッダ: タイプ オフセット 仮想Addr 物理Addr FileSiz MemSiz Flg Align LOAD 0x001000 0x08000000 0x08000000 0x02b6c 0x02b6c R E 0x1000 セグメントマッピングへのセクション: セグメントセクション... 00 .text
今度は、プログラムヘッダの情報が作られています。
bin2elf.shにENTRYコマンドも追加して使う
先ほどの bin2elf.sh に、ENTRYコマンドを追加します。
追加したのは、SECTIONSコマンドの直前に2行追加しました。シンボルの定義と、ENTRYコマンドです。
エントリポイントのアドレスは、第4引数に設定するようにしました。
このシェルスクリプトは、bin2elf_entry.sh としました。
#!/bin/sh # Convert a raw binary image into an ELF file suitable for loading into a disassembler cat > raw$$.ld <<EOF ENTRY_ADRS = $4; ENTRY(ENTRY_ADRS) SECTIONS { EOF echo " . = $3;" >> raw$$.ld cat >> raw$$.ld <<EOF .text : { *(.text) } } EOF CROSS_PREFIX=arm-none-eabi- ${CROSS_PREFIX}ld -b binary -r -o raw$$.elf $1 ${CROSS_PREFIX}objcopy --rename-section .data=.text \ --set-section-flags .data=alloc,code,load raw$$.elf ${CROSS_PREFIX}ld raw$$.elf -T raw$$.ld -o $2 ${CROSS_PREFIX}strip -s $2 rm -rf raw$$.elf raw$$.ld
それでは、このスクリプトで ELFファイルを作ります。
$ ./bin2elf_entry.sh stm32f4discovery_sample_objcopy.bin stm32f4discovery_sample_bin2elf_entry.elf 0x08000000 0x8000cf5
成功しました。ELFファイルの情報を確認します。
$ readelf -h stm32f4discovery_sample_bin2elf_entry.elf ELF ヘッダ: マジック: 7f 45 4c 46 01 01 01 61 00 00 00 00 00 00 00 00 クラス: ELF32 データ: 2 の補数、リトルエンディアン Version: 1 (current) OS/ABI: ARM ABI バージョン: 0 型: EXEC (実行可能ファイル) マシン: ARM バージョン: 0x1 エントリポイントアドレス: 0x8000cf5 プログラムヘッダ始点: 52 (バイト) セクションヘッダ始点: 15232 (バイト) フラグ: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 3 Section header string table index: 2 $ readelf -l stm32f4discovery_sample_bin2elf_entry.elf Elf ファイルタイプは EXEC (実行可能ファイル) です エントリポイント 0x8000cf5 There is 1 program header, starting at offset 52 プログラムヘッダ: タイプ オフセット 仮想Addr 物理Addr FileSiz MemSiz Flg Align LOAD 0x001000 0x08000000 0x08000000 0x02b6c 0x02b6c R E 0x1000 セグメントマッピングへのセクション: セグメントセクション... 00 .text
エントリポイントに設定したアドレスが入ってます!
おわりに
今回は、ELFファイルからバイナリファイル、バイナリファイルからELFファイルを構成してみました。
次回は、今回作ったバイナリファイルを、実際に、QEMU で動かしてみたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。