ARMのスタートアップルーチンをハック その2

ARMCCでのスタートアップが8割くらい完成しました。情報が少なく、正確な手法かは保障できませんがメモしておきます。一応行うことの概要はここに書いてある処理を自前で実装することです。

まず、最初にやることは、ベクタ、スタックの設定後に最初に呼び出す関数を__main()以外のものにすることです。これは単純な話で、アセンブリコードで

LDR     r0, =__main
MOV lr, pc
BX r0
といった感じになっているところを

LDR     r0, =BootMain    ; 自前のスタートアップルーチン(今回はC言語で作成)
MOV lr, pc
BX r0
という風にするだけ。



次に、リンカの出力する情報をCのソースコードから参照する必要があります。必要なのはROM領域とRAM領域のマッピング情報です。まずリンカの使用方法の要点から話しますと、armlinkにもGCCで使われているようなリンカスクリプトのようなものがあります。スキャッターローディングと呼ばれるモノで、まあ詳しくはリンク先を読んでください。リファレンスでは分散ロード等と記載されており、いまいち用語の統一がされておらず、なかなか混乱させられました。上記のスクリプトを使用しないでも単純なマッピングはリンカオプションでも可能です。このあたりに書いてあります。そしてリンカオプションでもうひとつ重要なものが!今回の作業でもっともハマったところです。armlinkは勝手にRAM領域の圧縮を行います。まあ、容量の節約という観点からは素晴らしい機能なんですが、しかし勝手に、しかもデフォルトでやられると困ります。圧縮されるとRAM領域内のアドレスが詰まるため、上手くROMからRAMへ初期値つき変数の初期値がコピーできませんでした。なのでarmlinkに"--datacompressor off"を指定してRAM領域の圧縮を無効にします。ここまででひとまずリンカの準備は終了とします。



さて、実際のCで実装されたコードに入っていきます。まず、リンカの出力したそれぞれの領域を表すシンボルにアクセスするには以下のようにします。リファレンスではこのあたりこのあたりに載っています。



extern int Load$$領域名$$Base;  // 読み出し領域のXXの開始アドレス
extern int Image$$領域名$$Base; // 実行領域のXXの開始アドレス
extern int Image$$領域名$$Limit; // 実行領域のXXの終端アドレス
extern int Image$$領域名$$Length; // 実行領域のXXの終端アドレス


領域名には分散ロードファイルで命名した領域名を指定します。リファレンスによるとリンカは分散ロードを使用しない場合はデフォルトで以下の命名を行うとの事。





  • ER_RO、読み出し専用の実行領域の場合
  • ER_RW、読み出し-書き込み実行領域の場合
  • ER_ZI、ゼロで初期化された実行領域の場合




しかし、ZI領域に関してはER_ZIではなく、単にZIと出力される…。RVDS3.1なのに…。まあ、いいか。なお、分散ロードファイルを使用する場合は__user_initial_stackheap()という関数を再実装する必要があるとの事です。リファレンスによると「__user_initial_stackheap() の標準実装では、Image$$ZI$$Limit の値が使用されます。」との事ですので、ZI領域の名前を"Image$$ZI$$Limit"になるように合わせておけば、再実装は必要ないのかもしれません。今回は分散ロードは使用していませんので詳細は書けませんが、分散ロードする場合は忘れずに再実装を行ってください。で、実際のコードは以下のとおり。

//armlinkの出力したシンボルのインポート
extern int Load$$ER_RW$$Base; // 読み出し領域のRWの開始アドレス
extern int Image$$ER_RW$$Base; // 実行領域のRWの開始アドレス
extern int Image$$ER_RW$$Limit; // 実行領域のRWの終端アドレス
extern int Image$$ZI$$Base; // 実行領域のZIの開始アドレス
extern int Image$$ZI$$Limit; // 実行領域のZIの終端アドレス

/**
* @brief RW、ZI領域を初期化する
*/
void InitRAM(void)
{
unsigned char* imgRW = (unsigned char*)&Image$$ER_RW$$Base;
unsigned char* imgRWLim = (unsigned char*)&Image$$ER_RW$$Limit;
unsigned char* ldRW = (unsigned char*)&(Load$$ER_RW$$Base);

// 初期値つき変数の初期化
while(imgRW < imgRWLim)
{
*imgRW = *ldRW;
imgRW++;
ldRW++;
}

imgRW = (unsigned char*)&Image$$ZI$$Base;
// ZI領域の0クリア
while(imgRW < (unsigned char*)&Image$$ZI$$Limit)
{
*imgRW = 0;
imgRW++;
}
}

とりあえず素直に書きました。一応ICE上で初期値がコピーされているのも確認できたので問題ないと思います。さて、あとは__rt_lib_init( )をどう実装するかだが…。こいつの実装に関する情報はいまいち得られず、ヘッダーを除いてみても

extern __value_in_regs struct __argc_argv
__rt_lib_init(unsigned /*heapbase*/, unsigned /*heaptop*/);
と定義されているだけ。コメントにも「スタック、ヒープの初期化の後に呼んでね」といったことが書いてあるが…。__user_initial_stackheap()とあわせてもう少し調査が必要そうです。ちなみに私の環境ではこの状態でも一応ソフトは動きました。