土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う

前回は、QEMU (ターゲットは STM32F4-Discovery)で動かしたELFファイルの内容を確認しました。

今回は、このELFファイルをバイナリファイルに変換して、デバッグ情報、シンボル情報を削除して、そこから、ELFファイルを再構築していきたいと思います。

それでは、やっていきます。

参考文献

STM32F4 のマニュアル

下記リンクのドキュメント→リファレンスマニュアル、ドキュメント→プログラミングマニュアルなどを参照してください。最近は、マニュアルが日本語化されていて、とても便利です。

デザイン/サポート | STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品

はじめに

「QEMUを動かす」の記事一覧です。良かったら参考にしてください。

QEMUを動かすの記事一覧

それでは、やっていきます!

STM32CubeProgrammer(STM32CubeProg)をインストール

ELFファイルを STM32F4-Discovery のファームウェア形式に変換するために STM32CubeProgrammer をインストールします。

以下から STM32CubeProgrammer をダウンロードします(ユーザ登録が必要です)。

www.st.com

ダウンロードできたら、解凍します。フォルダに入った状態で圧縮されてないので、フォルダを作った状態で解凍します。解凍できたら、セットアッププログラム(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 を起動します。

STM32CubeProgrammerを起動
STM32CubeProgrammerを起動

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

統計情報の取得確認画面
統計情報の取得確認画面

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

ELFファイルを指定する
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  513 00:13 stm32f4discovery_sample.bin
-rwxrwxr-x 1 daisuke daisuke     11116  513 00:19 stm32f4discovery_sample_objcopy.bin*

WinMergeで比較すると、先頭は全く同じです。ファイル末尾まで見てみると、STM32CubeProgrammer で変換した方は、後ろに 0xFF が埋められているだけでした。

もしかすると、STM32CubeProgrammer で変換した方は、スタックアドレスの 0x20200000 までファイルが作られているのかと思いましたが、402,781,952byte は、16進数で 0x1801F700 なので、RAMが始まる 0x20000000 にも達していませんので違うようです(アドレス情報が無いので前に詰められているのかもしれません)。

どちらにしても、STM32CubeProgrammer は、このような使い方は想定していないということだと思います。

WinMergeで比較する
WinMergeで比較する

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 で動かしてみたいと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。