RX220をrx-elf-gccで色々やった近況報告 続報

以前にこちらでprintf 系関数が動かん!」って言ってましたが、バッファサイズに関する問題の原因がわかりましたので本記事にて報告します。
あと、出来たバイナリのコードサイズを小さくする方法もご紹介します。

ことの発端

実は最近低レイヤ好きが昂じすぎて、マイコン用のリンカスクリプトを写経という謎なことをしていました。そこでふと「RX のあの問題、ひょっとしたら解決するんじゃね?」と思い挑戦したのが今日です。

printf 系関数に関する問題

問題の箇所

以前のページで、「脱 Windows 化したプロジェクト」とか言っていましたが、その環境下でのテストとなっています。ご留意ください。なお uart_printf 関数内部で char 型のバッファ変数を 256 個(256Bytes) 定義しています
さて、見ていたファイルはリンカスクリプト(HardwareDebug/RX220_GCC_HardwareDebug_auto.gsi)。そこで SECTIONS コマンド内にあるスタックに関する部分を見ると…

.ustack 0x200 : AT (0x200)
{
    _ustack = .;
} > RAM
.istack 0x100 : AT (0x100)
{
    _istack = .;
} > RAM

となっていました。おわかりいただけただろうか。何の事はない、.ustack セクションのサイズが不足していたんです。.ustack とは user stack のことで、CPU がユーザーモード(普通のモード)で動作している時に用いられるスタック領域のことです。一般に C 言語において関数内部のローカル変数はスタックを消費します。すなわち、スタックポインタをスライドし残り容量を減らすことで、そのでてきた領域にデータを格納するという動作をしています。この容量が不足して、何らかの例外が発生しプログラムがどっかに飛ばされてしまったんですね多分。どういう例外か実際に調べるには E1 エミュレータとかいうドチャクソ高いデバッグツールが必要になります。CMSIS-DAP みたいに手軽にできたら良いんですが…

また、これとは別にご覧の通り .istack というスーパーバイザーモード用のスタックもあるため、オーバーラップする部分は使うことが出来ません。食い込むと多分例外が発生します。

実際のスタックサイズの計算

さあその .ustack のサイズですが、 0x200 - 0x100 == 256 Byte となります。 ここでローカル変数を 128 個定義すると、この半分を使ってしまいますね。
そして更に vsprintf を内部で呼んでいるのでこの影響も大きいでしょう。
実際に出来上がったバイナリを rx-elf-objdump で見てみます。スタックポインタ(r0)に関係ありそうな所を抜き出してみました。

fffc0243 <_main>:
fffc0243:   60 c0                           sub #12, r0
...
fffc0370 <_uart_printf>:
fffc0370:   7e a6                           push.l  r6
fffc0372:   72 00 fc fe                     add #-260, r0, r0
...
fffc03db <__vsprintf_r>:
fffc03db:   71 00 98                        add #-104, r0, r0
...
fffc0420 <__svfprintf_r>:
fffc0420:   6e 6d                           pushm   r6-r13
fffc0422:   72 00 10 ff                     add #-240, r0, r0
...

RX アーキテクチャでは、スタックポインタは上方伸長であり、使えば使うほどアドレスが小さくなっていく仕様です。上に挙げたのは、実際に処理をする前にスタックが利用される部分のニーモニックコードです。
まず main 関数に入る時に -12、uart_printf 本体で -260(-256-4) されています。この -4 ですが、ポインタ変数を char *p と定義していたのでその分の 32bit 分も含めて一括で確保したのでしょう。
続いて vsprintf 内部です。内部ではまず _vsprintf_r が呼ばれ、いきなり -104 されています。処理は _svfprintf_r へと続き、r6 から r13 の 24Byte 分、そして更に -240 されています。

以上を計算しましょう。減った分を正の数として計算すると・・・

12 + 260 + 104 + 24 + 240 = 640Bytes

あらあら。そりゃ足りないわけですよね。
そもそもこのリンカスクリプトは e2studio で自動生成した GCC 用のものです。ですのでデフォで足りない感じだったのでしょう。

リンカスクリプトの修正

そこでリンカスクリプトを以下のように修正しました。

.ustack 0x400 : AT (0x400)
{
    _ustack = .;
} > RAM

これにより .ustack のサイズが 0x400 - 0x100 = 768Bytes へとなりました。これで大丈夫なはずです。実際にこれで動作を確認しました。やったね!

コードサイズを小さくする

続いて、コードサイズが e2studio 等で開発するときよりも異常に大きくなってしまう問題 です。こちらは簡単。1箇所直すだけです。

Makefile の修正

HardwareDebug/Makefilerx-elf-ld の引数として --gc-sections を追加してください。これで関数をまとめて .text セクションに配置するのではなく、関数ごとに別のセクション名 .text.hogehoge とする事になります。これにより使用していない関数はリンク時にガン無視されてサイズがきゅっと縮まります。
例えば僕は先程の uart_printfvsprintf を用いているのですが、これがまた容量を食います(20kB くらい?)。この uart_printf を main 関数から呼ばないようにした状態で上のオプションを追加するとコードサイズが 27071Bytes -> 1973Bytes と大幅に減少しました。やったね。

いやぁ、知識が非常に有効に出た本記事でしたね〜。長年悩み続けていた問題なのですごくうれしいです。さ、あとは printf 系関数の float 動作だが・・・