AVR32 C Project from templateのcrt0.x
AVR32 StudioでAVR32 C Project from templateからStandalone版のアプリケーションを指定すると、アセンブラ・ファイルsrc/SOFTWARE_FRAMEWORK/UTILS/STARTUP_FILES/GCC/crt0.xが作成される。trampoline.xのリセット・ハンドラ_trampoline→program_start→このcrt0.xの_stextへやってくる。
お勉強として、crt0.xを眺めてみる。行番号は私が付加したもので、実際には存在せず、行番号が飛んでいるのは不要と判断して削除したため。
54: .section .reset, "ax", @progbits
これ以降の定義はセクション.resetに配置される。allocatableとexecutableのフラグ、contains dataタイプが指定されている。
57: .global _start 58: .type _start, @function 59: _start: 60: // Jump to the C runtime startup routine. 61: lda.w pc, _stext 64: // _stext is placed outside the .reset section so that the program entry point 65: // can be changed without affecting the C runtime startup. 66: .section .text._stext, "ax", @progbits
関数_startが定義され、ほぼtrampoline.xの_trampolineとprogram_startと同様の内容であるから、本当はtrampoline.xは不要と思われる。実際のリセット・ハンドラは_trampolineであり、_startはロード・モジュールに取り込まれていない。これはリンク・オプションに「-Wl,-e,_trampoline」があるためらしい。「らしい」というのは、このオプションの正体がよく判らないから(ドキュメントにも無い)。後で調べてみよう。
個人的には複数ファイルにセクション.resetのグローバル関数を定義することは嫌いだ。どれが本当のリセット・ハンドラか判りづらいからだ。
69: .global _stext 70: .type _stext, @function 71: _stext:
続いてリセット・ハンドラからジャンプしてくる関数_stextの定義(関数といっても戻ってくることはないけど)。
73: lda.w sp, _estack
スタック・ポインタをシンボル_estack(00008000, 内蔵SRAM末尾+1)の位置に割り付ける。lda.wは仮想オペコードで、リンク時の評価により、異なるオペコードに展開される。セクション_estackがどこで定義されているのか判らない。たぶんリンク・オプションの「-Wl,--gc-sections」辺りが怪しいが、これも宿題。
75: // Set up EVBA so interrupts can be enabled. 76: lda.w r0, _evba 77: mtsr AVR32_EVBA, r0
例外ハンドラ・テーブルがシンボル_evbaに(たぶん)あり、このアドレスをレジスタEVBA(Exception Vector Base Address)に設定する。システム・レジスタにアクセスするためmtsr(Move To System Register)命令を使用する。
79: // Enable the exception processing. 80: csrf AVR32_SR_EM_OFFSET
例外ハンドラ・テーブルを設定したので、例外処理を有効化する。
82: // Load initialized data having a global lifetime from the data LMA. 83: lda.w r0, _data 84: lda.w r1, _edata 85: cp r0, r1 86: brhs idata_load_loop_end 87: lda.w r2, _data_lma 88: idata_load_loop: 89: ld.d r4, r2++ 90: st.d r0++, r4 91: cp r0, r1 92: brlo idata_load_loop 93: idata_load_loop_end:
C言語の初期値付きstatic/グローバル変数のような初期値つき変数領域を初期化する。内蔵SRAMの該当領域が_dataから_edata-1に存在する(今回の例では_data(0x00000000)、_edata(0x00000008))。85〜86行目は、もし_data≧edataなら初期値つき変数領域が無いので以後の処理を飛ばす。87〜92行目で、初期データが_data_lma(0x800024d0)に存在するのでr2に入れ、_dataを代入したr0の間でコピーする。コピー終了は_edataを保持するr1を越えるまで。ldとst命令のデータ・サイズが.d=4byte=64bitなので、データ転送にはR4だけでなくR5も使われている。記述としてはこうなるんだろうけど、「R4-R5」みたいに書けないとバグの温床になるから嫌だな。
ところで「LMA」とは何の略だろう?ダンプ・リストでは「VMA」と並んでアドレスを示しているので、「Local Memory Address」らしく、「Virtual Memory Address」とは異なり実アドレスを表しているようだが、それならPMAになりそうだ。Architecture Manualでは「Large Memory」という言葉も出てくるけど...うーん、判らない。
95: // Clear uninitialized data having a global lifetime in the blank static storage section. 96: lda.w r0, __bss_start 97: lda.w r1, _end 98: cp r0, r1 99: brhs udata_clear_loop_end 100: mov r2, 0 101: mov r3, 0 102: udata_clear_loop: 103: st.d r0++, r2 104: cp r0, r1 105: brlo udata_clear_loop 106: udata_clear_loop_end:
未初期化変数領域を0クリアしている。詳細は、1つ手前の初期化変数領域と似ているので省略。
108: #ifdef CONFIG_FRAME_POINTER 109: // Safety: Set the default "return" @ to the exit routine address. 110: lda.w lr, exit 111: #endif
マクロCONFIG_FRAME_POINTER(デフォルトでは未定義)が定義されているとLRレジスタが初期化される。LRレジスタはreturnするアドレスが格納されているが、コールせず(LRレジスタに戻り先アドレスが格納されない)にリターンすると明後日の方向に飛んでいってしまう。こういう不慮の事故を防ぐ安全策として予めexitへのアドレスを格納している。Fail to safeの考え方だが、exitに飛んだからといって安全か(問題無いか)というのは別の話。
113: // Start the show. 114: lda.w pc, main
最後にユーザ・プログラムのmainに飛んでいく。
あれ?!「_main」じゃない!!
C言語で作成されたシンボルはコンパイルされた時点(アセンブル記述に変換されたとき)で、シンボルの前に「_」が付く。これはUNIX以来の伝統だ。当初アセンブラでプログラムされていたところにUNIXのC言語が登場したが、ライブラリ(当然アセンブラで作成)のシンボルと衝突することがしばしばあった(シンボル名の長さが8文字(たぶん)という制限の影響が大きい)。これを自動的に回避する対処方法だった。だからアセンブラからC言語のシンボルを参照するには「_」を付加する必要があるし、C言語側から呼び出すアセンブラのシンボルには予め「_」を付けて定義するだが、AVR32-gccでは不要なのか?
ちなみにコンパイラ内部で利用するシンボルは、先頭に「__」(2文字)を付けるそうな。
このソースは、「C言語スタートアップ・ルーチン」なので、C言語のアプリケーションやライブラリに関する最低限の処理しか実施していない。ハードウェアの初期化など、例外ハンドラ・テーブルとの絡みがあるEVBAとLRレジスタのみ初期化する。
OS上で稼働するアプリケーションなら、main関数は直ちにアプリケーションの機能を実現するところだが、スタンドアローンのmain関数はまずハードウェアの初期化から実施する必要がある。