以前にこちらで「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/Makefile
の rx-elf-ld
の引数として --gc-sections
を追加してください。これで関数をまとめて .text
セクションに配置するのではなく、関数ごとに別のセクション名 .text.hogehoge
とする事になります。これにより使用していない関数はリンク時にガン無視されてサイズがきゅっと縮まります。
例えば僕は先程の uart_printf
で vsprintf
を用いているのですが、これがまた容量を食います(20kB くらい?)。この uart_printf
を main 関数から呼ばないようにした状態で上のオプションを追加するとコードサイズが 27071Bytes -> 1973Bytes と大幅に減少しました。やったね。
〆
いやぁ、知識が非常に有効に出た本記事でしたね〜。長年悩み続けていた問題なのですごくうれしいです。さ、あとは printf
系関数の float 動作だが・・・