前回は、QEMU (ターゲットは STM32F4-Discovery)で動かしたサンプルソースのリンカスクリプトの内容を確認しました。
今回は、これまで使ってきたサンプルソースをビルドして生成された ELFファイル の内容を見ていきたいと思います。
それでは、やっていきます。
参考文献
マニュアル
STM32F4 のマニュアル
下記リンクのドキュメント→リファレンスマニュアル、ドキュメント→プログラミングマニュアルなどを参照してください。最近は、マニュアルが日本語化されていて、とても便利です。
デザイン/サポート | STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品
リンカのマニュアル
https://sourceware.org/binutils/docs/ld/
はじめに
「QEMUを動かす」の記事一覧です。良かったら参考にしてください。
QEMUを動かすの記事一覧
それでは、やっていきます!
バイナリエディタ(HxD)のインストール
ELFファイルの内容を確認するのに必要なのは、まずはGNUツールとバイナリエディタがあれば十分だと思います。
GNUツールは、Ubuntu ならデフォルトで入ってるかもしれません(binutils の例えば readelf)。第1回の STM32(ARM Cortex-M)をQEMUで動かす(環境構築編) - daisukeの技術ブログ でインストールしたクロスコンパイラにも入ってます(arm-none-eabi-readelf)。
あとは、直接ファイルを見るために、バイナリエディタが必要です。バイナリエディタなら何でもいいですが、ここでは、HxD をインストールします。
mh-nexus.de
Version は、2.5.0.0 が最新のようです。
ダウンロード(HxDSetup.zip)したら、中に入って、HxDSetup.exe を実行します。
言語の選択画面になるので、日本語のままで、OKをクリックします。
インストール画面が表示されるので、次へをクリックします。
ライセンスの確認画面が表示されるので、「同意する」にチェックを入れて、次へをクリックします。
インストール先の指定が表示されるので、問題なければ次へをクリックします。
デスクトップ、クイック起動にアイコンを作成するかを聞かれるので、お好みで選んで、次へをクリックします。
インストール準備完了が表示されるので、インストールをクリックします。
インストールが完了しました。完了をクリックします。
STM32F4-DiscoveryのSTM32F407VGのデータシート情報
データシートは、STM32の他のマニュアルと同じく「https://www.stmcu.jp/design/」からダウンロードできます。
データシートによると、今回ターゲットにしている「STM32F4-Discovery」の「STM32F407VG」は、1MBのFlashメモリと、192KBのRAMを搭載しています。
素直に割り当てると、Flashメモリは、0x08000000 から 0x080FFFFF になります。RAMは、データシートを見ると、ちょっと変則的で、3か所に、0x10000000-0x2000FFFF(64KB)と 0x20000000-0x2001BFFF(112KB)と 0x2001C000-0x2001FFF(16KB)になっています。
これを踏まえて、ELFファイルの内容を見ていきます。
ELFファイル
参考文献の「Binary Hacks ―ハッカー秘伝のテクニック100選」を見ながらやっていきます。古い本ですが、この本の代わりは見当たりませんでした。
ELFファイルの ELF は、「Executable and Linking Format」の略で、実行可能バイナリ、オブジェクトファイルなどで使われているファイルフォーマットのことです。
fileコマンドでELFファイルかどうかを確認することが出来ます。今回のサンプルソースの実行バイナリを対象にfileコマンドを実行してみます。
$ file stm32f4discovery_sample.elf
stm32f4discovery_sample.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
32bit、リトルエンディアン(LSB)、ARM用の実行可能バイナリで、デバッグ情報が残ってるという情報が得られました。
ELFヘッダを確認する
日本語で表示されたので、readelf を使いました。ELFヘッダは、readelfコマンドの「hオプション」で確認することが出来ます。
ELFヘッダは、ELFファイルのヘッダで、後続のプログラムヘッダやセクションヘッダの構造や位置を示しています。
重要なところだけ説明します。
- 先頭の4byteが特徴的で、ASCIIコードで「ELF」が入っています
- ARM用の実行可能ファイル
- エントリポイントが 0x8000cf5 となっています
- このELFヘッダの長さは52byteとなっています
- プログラムヘッダの始点が52byteなので、ELFヘッダの後にプログラムヘッダが続いていることが分かります
- プログラムヘッダの長さは32byteで3個(32×3=96byte)となっています
- セクションヘッダの長さは40byteで26個(40×26=1040byte)となっています
$ readelf -h stm32f4discovery_sample.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
型: EXEC (実行可能ファイル)
マシン: ARM
バージョン: 0x1
エントリポイントアドレス: 0x8000cf5
プログラムヘッダ始点: 52 (バイト)
セクションヘッダ始点: 342284 (バイト)
フラグ: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 3
Size of section headers: 40 (bytes)
Number of section headers: 26
Section header string table index: 25
ELFヘッダの長さは52byteとなっています。バイナリエディタで開いて、ELFヘッダの部分を選択したところです。
プログラムヘッダ
プログラムヘッダは、各プログラムをどのメモリアドレスにロードするかの情報を示しています。
プログラムヘッダは、readelfコマンドの「lオプション」で確認することが出来ます。
3つのプログラムの構成になっているようです。最初が TEXTセクション(命令、ROM)で、2番目が DATA、BSSセクション(RAM)で、3番目はスタック領域(RAM)になっています。
- オフセット:このELFファイル内の開始位置を示しています
- 仮想アドレス:実行時のメモリアドレス
- 物理アドレス:ロードするメモリアドレス
- ファイルサイズ:このELFファイルの消費しているサイズ(ROMサイズ)
- メモリサイズ:ターゲットの消費するサイズ?(RAMサイズ)
下層アドレスと物理アドレスが分かりにくいですが、リンカのマニュアルによると、この2つのアドレスは通常同じになるが、dataセクションでは異なる値が入るとのことです。
dataセクションは、初期値付きのグローバル変数の初期値を格納する領域です。実行時(スタートアップルーチン実行時)に、ROMから初期値を読みだして、RAM(dataセクション)にコピーします。
このROM側のアドレスが物理アドレス(ロードするアドレス)で、RAM側のアドレスが仮想アドレス(実行時のアドレス)を格納しているとのことです。
ELFファイルと一緒に生成しあれたマップファイル(stm32f4discovery_sample.map)を見ると、命令が格納されているTEXTセクッション部分は 0x08000000-0x08002AF8 までで、DATAセクション部分は、0x20000000-0x20000074 で、BSSセクションはその続きで、0x20000168 まで続いていました。
あとは、スタック領域です。16KB の SRAM の後ろの方を使っているようです。
$ readelf -l stm32f4discovery_sample.elf
Elf ファイルタイプは EXEC (実行可能ファイル) です
エントリポイント 0x8000cf5
There are 3 program headers, starting at offset 52
プログラムヘッダ:
タイプ オフセット 仮想Addr 物理Addr FileSiz MemSiz Flg Align
LOAD 0x010000 0x08000000 0x08000000 0x02af8 0x02af8 RWE 0x10000
LOAD 0x020000 0x20000000 0x08002af8 0x00074 0x00168 RW 0x10000
LOAD 0x00f700 0x2001f700 0x2001f700 0x00000 0x00100 RW 0x10000
セグメントマッピングへのセクション:
セグメントセクション...
00 .isr_vector .text .rodata .init_array .fini_array
01 .data .bss
02 ._user_heap_stack
プログラムヘッダの長さは 96byte となっています。バイナリエディタで開いて、プログラムヘッダの部分を選択したところです。
実際に、ELFファイルの TEXTセクションを見てみたいと思います。
プログラムヘッダの最初の領域が TEXTセクションでありオフセットは 0x10000 となっています。つまり、ELFファイルの先頭から 0x10000 の位置に TEXTセクションが存在しているはずです。
では、TEXTセクションには、どのようなデータが格納されているのでしょうか。
リファレンスマニュアルの「2.4 ブート設定」によると、BOOTピンによって、0x00000000 にエイリアスされるメモリを変更することが出来るとあります。通常は、Flashメモリです。
そして、リセット時、0x00000000 からスタックアドレスを SPレジスタにロードして、0x00000004 から実行するアドレス(リセットハンドラ)を取得して実行を開始するとあります。
つまり、TEXTセクションの最初の 4byte はスタックアドレスで、次の 4byte リセットハンドラのアドレスが格納されていることになります。
それでは、実際にバイナリエディタで、ELFファイルの先頭から 0x10000 の位置を見てみます。
先頭の 4byte はスタックアドレスで、0x20020000 となっています。次の 4byte はリセットハンドラのアドレスは 0x08000CF5 となっています。
プログラミングマニュアルの「2.3.4 ベクタ・テーブル」によると、ハンドラが Thumbコードの場合は最下位ビットを 1 にする必要があるとあります。よって、リセットハンドラのアドレスは 0x08000CF4 ということになります。
ちゃんと TEXTセクションのデータが格納されていました。
次は、実際に、デバッガでリセットハンドラの先頭でブレークして、PC(プログラムカウンタ)を確認してみます。すると、確かに、リセットハンドラのアドレスは 0x08000CF4 でした、スッキリです(笑)。
一応、2番目のRAMのセクションも見てみます。サイズは 0x74 で、グローバル変数の初期値が入っていると思います(具体的な値は分かりません)。それを超えたところには、何か別のものが入っていそうです。
3番目のスタックの領域も見てみましたが、FileSiz が 0 なので、何も入ってないはずで、実際は 0 でした。
セクションヘッダ
セクションヘッダは、各セクションの情報(アドレス、サイズ、アライメントなど)が格納されています。
セクションヘッダは、readelfコマンドの「Sオプション」で確認することが出来ます。
セクション名、タイプ、開始アドレス、ファイル内のオフセット、サイズ、各種フラグが確認できます。
$ readelf -S stm32f4discovery_sample.elf
There are 26 section headers, starting at offset 0x5390c:
セクションヘッダ:
[番] 名前 タイプ アドレス Off サイズ ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .isr_vector PROGBITS 08000000 010000 000188 00 A 0 0 4
[ 2] .text PROGBITS 080001c0 0101c0 00284c 00 AX 0 0 64
[ 3] .rodata PROGBITS 08002a0c 012a0c 0000e4 00 A 0 0 4
[ 4] .ARM.extab PROGBITS 08002af0 020074 000000 00 W 0 0 1
[ 5] .ARM PROGBITS 08002af0 020074 000000 00 W 0 0 1
[ 6] .preinit_array PREINIT_ARRAY 08002af0 020074 000000 04 WA 0 0 1
[ 7] .init_array INIT_ARRAY 08002af0 012af0 000004 04 WA 0 0 4
[ 8] .fini_array FINI_ARRAY 08002af4 012af4 000004 04 WA 0 0 4
[ 9] .data PROGBITS 20000000 020000 000074 00 WA 0 0 4
[10] .bss NOBITS 20000074 020074 0000f4 00 WA 0 0 4
[11] ._user_heap_stack NOBITS 2001f700 02f700 000100 00 WA 0 0 1
[12] .ARM.attributes ARM_ATTRIBUTES 00000000 020074 00002a 00 0 0 1
[13] .comment PROGBITS 00000000 02009e 000033 01 MS 0 0 1
[14] .debug_info PROGBITS 00000000 0200d1 013a83 00 0 0 1
[15] .debug_abbrev PROGBITS 00000000 033b54 003dc1 00 0 0 1
[16] .debug_aranges PROGBITS 00000000 037918 0004a0 00 0 0 8
[17] .debug_line PROGBITS 00000000 037db8 0069ff 00 0 0 1
[18] .debug_str PROGBITS 00000000 03e7b7 008d4f 01 MS 0 0 1
[19] .debug_loc PROGBITS 00000000 047506 0064d7 00 0 0 1
[20] .debug_frame PROGBITS 00000000 04d9e0 000e38 00 0 0 4
[21] .debug_ranges PROGBITS 00000000 04e818 001130 00 0 0 1
[22] .debug_macro PROGBITS 00000000 04f948 001d10 00 0 0 1
[23] .symtab SYMTAB 00000000 051658 001850 10 24 256 4
[24] .strtab STRTAB 00000000 052ea8 00094d 00 0 0 1
[25] .shstrtab STRTAB 00000000 0537f5 000115 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), y (purecode), p (processor specific)
おわりに
今回は、実行可能ファイルのELFファイルの内容を見てみました。プログラム本体やデータ以外に、たくさんの情報で管理されていることが分かりました。
次回は、ELFファイルをバイナリファイルに変換したり、その逆のバイナリファイルをELFファイルに変換したりしてみたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。