前回は、QEMU (ターゲットは STM32F4-Discovery)で動かしたサンプルソースのスタートアップルーチン以外の内容を確認しました。
今回は、サンプルソースのスタートアップルーチンの内容の確認と、アセンブラの動作の確認をしていきます。
それでは、やっていきます。
参考文献
STM32F4 のマニュアル
下記リンクのドキュメント→リファレンスマニュアル、ドキュメント→プログラミングマニュアルなどを参照してください。最近は、マニュアルが日本語化されていて、とても便利です。
デザイン/サポート | STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品
GNU アセンブラのマニュアル
英語しか見つかりませんでした。
https://sourceware.org/binutils/docs/as/
https://sourceware.org/binutils/docs/as/ARM-Directives.html
はじめに
「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の動作を変える
今回も、Interface のサンプルソースを使わせていただきます。
サンプルソースは、Interfaceのホームページからダウンロードします。
下記の「7月号 仮想から実機まで マイコン開発入門」の「特集 第3部第1章 エミュレータQEMUを活用した開発の手引き」の「関連ファイル一式」をダウンロードします。
それでは、やっていきます!
startup.S
スタートアップルーチンとは、マイコンが起動して最初に動くプログラムです。
理解する上で重要なレジスタ一覧と、メモリマップを貼っておきます(プログラミングマニュアルより)。


先頭からベクタテーブルまで
早速、スタートアップルーチンの先頭から見ていきます。
.syntax unified:ARM命令とThumb命令で、統一した書き方をする宣言.thumb:Thumb命令で機械語を生成させる宣言.section .isr_vector,"a",%progbits:セクションの定義、.isr_vectorはリンカスクリプト(stm32f407vg.ld)で定義されている、"a"はセクション割り当て可能、%progbitsはこのセクションにdataを含んでいることを示す.align 2:2byteアライメントに合わせます(既にアライメントが合ってる場合は何もしない).globl __Vectors:外部ファイルに公開する宣言
これ以降は、先頭がスタックアドレスの初期値、その後、4byteごとに、例外、割り込みの飛び先のアドレスが格納されています。
/* Definition of Section */ .syntax unified .thumb .section .isr_vector,"a",%progbits .align 2 .globl __Vectors __Vectors: .long __main_stack_start /* Main stack pointer (MSP) */ .long Reset_Handler /* Reset Handler */ .long NMI_Handler /* NMI Handler */ .long HardFault_Handler /* Hard Fault Handler */ .long MemManage_Handler /* MPU Fault Handler */ .long BusFault_Handler /* Bus Fault Handler */ .long UsageFault_Handler /* Usage Fault Handler */ (以降のベクタテーブルは省略)
リセットハンドラからモード設定まで
先ほどのベクタテーブルにあったリセットハンドラの飛び先です。
.text:テキストセクションの宣言.thumb_func:この次にあるシンボルがThumbエンコードされた関数であることの宣言.type Reset_Handler, %function:このシンボル(この場合はReset_Handler)のタイプ(この場合は%function)を設定する(通常のラベルではなく関数であるという宣言)Reset_Handler::関数ラベル(飛び先)
mrsは、controlレジスタの値をr0レジスタにコピーしています。__process_stack_start と __main_stack_start はリンカスクリプト(stm32f407vg.ld)でアドレスが定義されています。
orrは論理和で、controlレジスタのbit1をセットするためです(スタックポインタはPSPを有効にする)。bicはビットクリアで、controlレジスタのbit0をクリアするためです(特権モードにしてる)。
よく分からなかったのが、コメントには非特権レベルにするとありますが、特権レベルになってると思います。main関数でブレークポイントを設定して、デバッガでcontrolレジスタを確認しましたが、0x02となっていて、特権レベルに見えます。まぁ気にしないことにします。
b(分岐)の前の最後の3行のmrsは、それぞれのレジスタに設定した内容(汎用レジスタの内容)を転送しています。
.text .align 2 /* Reset Handler */ .global Reset_Handler .thumb_func .type Reset_Handler, %function Reset_Handler: /* スレッドモード(PSP, 非特権レベル)で動作するように設定 */ mrs r0, control ldr r1, =__process_stack_start orr r0, r0, #0x02 ldr r2, =__main_stack_start bic r0, r0, #0x01 msr psp, r1 msr msp, r2 msr control, r0 b l1
data、bssセクションの初期化から最後まで
.ltorg:リテラル定数をこの位置にダンプさせる宣言
dataセクションは初期値付きのグローバル変数の領域です。_sidata は初期値を格納している領域で、_sdata が dataセクションの開始アドレス、_edata は終了アドレスです。
_sidata と _sdata のアドレスを比較して、同じだったら該当するデータがないので、bssセクションの初期化に飛びます。そうでなければ、1: のラベルに進みます。
1: のラベルからは、実際に初期値をdataセクションにコピーします。_sdata と _edata を比較して異なる場合(まだdataセクションが続いてる)、r3レジスタを介してコピーします。itt は、if thenの命令で、neが真ならneが付いた命令を実行します。ldrne と strne を実行すると、r0とr1は4byteインクリメントされます。これでdataセクションの初期化が完了します。
bssセクションの初期化も同じような感じです。bssセクションの初期化が完了すると、main関数にジャンプします。
.ltorg .thumb l1: /* Copy the data sections. */ ldr r0, =_sidata ldr r1, =_sdata ldr r2, =_edata cmp r0, r1 beq 2f 1: cmp r1, r2 itt ne ldrne r3, [r0], #4 strne r3, [r1], #4 bne 1b 2: /* Zero fill the bss segment. */ mov r0, #0 ldr r1, =_sbss ldr r2, =_ebss 3: cmp r1, r2 bge 4f str r0, [r1], #4 b 3b 4: /* Call the application's entry point.*/ ldr r2, =main bx r2
この後は、各例外ハンドラの同じ記述が続くだけなので、省略します。
デバッガでスタートアップルーチンの動きを確認する
ソースコードで確認した内容を、実際にデバッガを使って確認してみます。
事前に、Windows→Show View で、「Disassemblyビュー」と「Registersビュー」を起動しておきます。
下図は、main関数で停止した状態の画面です。

startup.S を開き、Reset_Handler の先頭にブレークポイントを設定して、再実行します。ブレークポイントの設定は、行番号の少し左あたりをダブルクリックすると設定できます(下図)。

あとは、ステップ実行していくと、スタートアップルーチンの動作が確認できます。
おわりに
今回は、スタートアップルーチンの内容の確認と、実際にスタートアップルーチンをデバッガでステップ実行して、理解を深めました。これで、サンプルソースの全体を見たことになります。
次回は、リンカスクリプトの内容を見ていきたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。