「e2studio + GCC for Renesas RL78」ROMサイズが少し大きくなるといろいろ出る不具合【 near領域のROMサイズに注意しましょう】

【解決後のコメント】この問題はRL78の nearアドレッシング可能領域外にコンパイラがnearアドレッシング定数を配置してしまうことで起きる暴走でした。
単純化したコードでは、定数のサイズを加減して原因調査し、Rsply3までに解決しました。
コンパイル後のMAPファイルで(.rodata)セクション(nearアドレッシング)のサイズがRL78のミラー領域を超えていないか、確認することで異常動作を防止できます。

しかし定数エリアのサイズがそれほど大きくないにもかかわらず、プログラムコードのサイズが大きくなると暴走します。
4番目のreply で説明していますが、プログラムコードのサイズが大きくなると nearアドレッシング可能なROM領域をプログラムコードがつぶしてしまうため、メモリーマッピング的に失敗していました。
結局のところリンカーファイルを手直しで編集して、 nearアドレッシング可能領域を確保する必要がありました。

-------------------------------

GCC for Renesas RL78の不具合と思われる現象で困っています。
e2studioで使うコンパイラのツールチェーンは「GCC for Renesas RL78」バージョン4.9.2.201902です。

単純化したので、プロジェクトを新規作成してすぐ再現できます。
事の発端はCS+とCCRLコンパイラの組み合わせででは問題なく動いていたプログラムを、GCC RL78の開発環境へ移したら暴走したことです。
GCC RL78の環境に変える理由は、CCRLコンパイラの無償評価期間が過ぎて64KB以上のプログラムを開発できなくなったためです。
CPUはRL78/G14 ROMサイズが256KBの104GJ で、エミュレータE1を接続してデバッグしています。

暴走するのはCのプログラムですが、調べると switch文 のところで暴走します。
動かない原因を探るためにコードをどんどん落としていくと、コードサイズが二十数KB程度のプログラムなら問題なく動くことが分かりました。
どうもコードサイズに関係する不具合のようなので、さらに単純化しました。
割り込み、周辺機能は一切使用せずハードウェアデバッグ機能のみ使用する。
調査に使用するソースは単純なCのソース一本だけ。
異常を確認するソース "r_main.c" は次のような簡単なものになっています。


const long Array1[2048] = {1, 1, 0, 0, 0, 0, 0 ... 略};    // 配列定数(1行でROM領域8Kバイト)
const long Array2[2048] = {1, 1, 2, 0, 0, 0, 0 ... 略};
const long Array3[2048] = {1, 1, 0, 2, 0, 0, 0 ... 略};
const long Array4[2048] = {1, 1, 0, 0, 5, 0, 0 ... 略};
const long Array5[2048] = {1, 1, 0, 0, 0, 10, 0 ... 略};

char c;
unsigned short i;
long lwk1, lwk2, lwk3, lwk4, lwk5;

char SwtchTest(char ac);
void R_MAIN_UserInit(void);

/***********************************************************************************************************************
* メイン関数
***********************************************************************************************************************/
void main(void)
{
    R_MAIN_UserInit();

    lwk1 = 0; lwk2 = 0; lwk3 = 0; lwk4 = 0; lwk5 = 0;

    for(i = 0; i<2; i++){
        lwk1 += Array1[i];
        lwk2 += Array2[i];
        lwk3 += Array3[i];
        lwk4 += Array4[i];    // 行をコメント化するとROM定数エリアが8Kバイト減る
        lwk5 += Array5[i];      <--------- ※1. Array5[0] にとんでもない大きな数値が代入される
    }

    lwk1 = lwk1 + lwk2 +lwk3 + lwk4 + lwk5;

    while (1U)
    {
        // test switch case
        c = 1;
        c = SwtchTest(c);     <--------- ※2. swich文に入ったとたんに暴走してプログラムカウンタが 0 になる(0番地へジャンプする)
    }
}

/***********************************************************************************************************************
* Function Name: R_MAIN_UserInit
***********************************************************************************************************************/
void R_MAIN_UserInit(void)
{
    EI();
}

char SwtchTest(char ac)     <--------- ※ swich文の関数
{
    char ret;

      switch(ac)
       {
          case 0:
              ret = ac +1;
             break;
          case 1:
              ret = ac +2;
             break;
          case 2:
              ret = ac +3;
             break;
          case 3:
              ret = ac +4;
             break;
          case 4:
              ret = ac +5;
             break;
          default:
             return -1;
       }
       return ret;
}
以上


説明

異常現象は上記ソース中にコメントしたように下記2点です
※1. Array5[0] にとんでもない大きな数値が代入される
※2. swich文に入ったとたんに暴走してプログラムカウンタが 0 になる(0番地へジャンプする)

使用する配列定数(const Array1[])が3つまでなら、ROMサイズは24KB程度でプログラムは正常に動きます。(ROMサイズが24KB 、プログラムサイズは828バイト)
使用する配列を4つにすると、swich文で暴走します。(ROMサイズが32KB 、プログラムサイズは896バイト)
使用する配列を5つにすると、swich文で暴走するほか、※1.の配列の参照値がとんでもない値になります。(ROMサイズが41KB 、プログラムサイズは978バイト)

ROMサイズの調整は、ソース中の  lwk4 += Array4[i]; といった1行をコメント化すると1行当たり8Kバイト分のROM定数エリアを使わなくなります。
(これはconst Array4[... で定義した定数配列が使われないのでコンパイラが定数として実体化させないため)

単純化したので、プロジェクトを新規作成してすぐ再現できます。
このように明らかにROMサイズと関係して不具合が発生しています。(今回はROM定数エリアでサイズを調整しましたが、プログラムコードサイズでも同じことが起こるようです)

対策としては、switch文を全て if文に変えても、定数の扱いでおかしな現象が残ってしまうため、GCCが怖くて使えない状態で困っております。

--2019年8月11日 追記--

ハードウェアがない環境でRL78 Simulatorを使用したデバッグでも同様の現象を確認出来ます。
またGCCのバージョン4.9.2.201801でも同じ、Cの言語仕様は、Defaultの"GNU ISO C90" に加えて"GNU ISO C99"、"GNU ISO C11" で試しても同様でした。
switch文のcase分岐数を減らすと暴走しなくなります。(逆アセンブラのコードもかなり変化します)

  • eokayamaさん、こんにちは。NoMaYと申します。

    RL78/G14の104GJであれば、const変数は基本は最大でも27.75Kバイトしか取れない筈、ですよ。(RL78のアーキテクチャ&CC-RLの設計思想&GNURL78の設計思想の点では、以下のハードウェアマニュアルの画面コピーのMirrorと書かれた空間を通して使用することになっているからです。)

    ただし、CC-RLではメモリモデルのオプションのひとつに、「ROMデータをfar領域に配置する」というオプションがありますが、ひょっとして、これを設定していませんでしたか?(とはいえ、私自身は、このオプションを使ったことがないので、設定した場合に生成されるコードの詳細は分かっていません。だいたいの予想はつきますが。)

    もしそうであれば、GNURL78には同様のオプションが無いようですので、変数宣言をconst farにするしかないと思われます。

    eokayamaさんのプログラムがGNURL78で動作しない原因は他にもあるかも知れませんが、まず、この点を変更する必要があるのではないかと思います。他方、このようなことに関してさえ、GNURL78ではワーニングが出ませんので、結局、「怖くて使えない」という印象は変わることが無いようにも思います。安心を望むのであれば、もしコードが64KBを超えている原因が巨大なconst変数群にあるのであれば、以下の資料(H8コンパイラ+HEWでの資料ですが)の裏技その2と裏技その3を参考にして、CC-RLで同様の手法で何とかする方法も可能ではないかと思います。(手間と引き換えに安心を取る、ということです。)

    「今すぐ使える!H8マイコン基板 増補版」第22章 - 無償評価版HEWを最大限に活用する三つの「裏技」
    toragi.cqpub.co.jp/Portals/0/support/2010/H8/mihon/191_197_(22).pdf


    RL78/G14 ユーザーズマニュアル ハードウェア編
    www.renesas.com/jp/ja/search/keyword-search.html#q=r01uh0186
    r01uh0186jj0330-rl78g14.pdf


    [追記]

    なお、以下の画面コピーの通り、CC-RLではconst変数群が溢れた場合にはエラーになります。

    当方特有の事情でCS+ V8.01 & CC-RL V1.02を使っています


    [追記その2]

    あと、CC-RLで「ROMデータをfar領域に配置する」というオプションを使っていなかった場合、CC-RLからGNURL78に移植して暴走した原因として考えられるのは、const変数群が27.75Kバイトぎりぎりに収まっていたのとswitch文では分岐テーブルが使われていなかったのとでCC-RLではエラーにならなかったけれど、GNURL78ではswitch文で分岐テーブルが使われていたのとそれがconstセクションに出力されているのとでMirror空間から溢れてしまった、という可能性です。

  • こんにちは

    配列定数の扱いでCCRLとGCCでは何か相違があるのではないでしょうか。以下はRXのコンパイラに関するスレッドですがここに書かれているような点でCCRLとGCCで微妙に仕様が違うとか。。。
    japan.renesasrulz.com/.../thread

    問題が発生するもののMAPファイルをCCRLとGCCで比べてみてはどうでしょうか。
  • NoMayさん、じま さん、RePlyありがとうございます。
    結論から言うとアドバイスを参考に __far型修飾子を追加することにより暴走を停めることができました。

    自分もメモリマッピング上の問題がありそうと薄々感じていましたが、これ以上頑張って調べるよりアドバイスをもらおうと今回投稿した次第です。
    CPUの選択までさかのぼってお話しすると、コードサイズが大きくなりそうだったのでメモリの大きめのCPUを捜していると、RXまで使わなくてもRL78でROMサイズの大きなマイコンもあるじゃん!
    というノリで選んだCPUですが、RL78マイコンには慣れていなくて。
    恨み節ですが、エラーやワーニングが出ないなら賢いコンパイラが上手に処理してくれていると思っちゃいます。
    今回の問題が上手く処理できなかったら、敗北を認めて他のマイコンに替える方向に半歩踏み出しているところ。RX110を検討中(笑)
    とはいえRL78シリーズは特徴ある魅力的なハードなので、こんな事で使わなくなるのはもったいない話です。
    CC-RLを使い続けるための裏技って涙ぐましい(笑)ですね!(そこまでやるか)
    某M社などは無償でコードサイズ制限無し(最適化機能で制限)なので、ルネサスも頑張ってほしいところなんですが。

    本題に戻って、27.75Kバイトのミラー領域は効率良いアドレスのための領域なので、 __nearアドレッシングの const領域なら制約されるという意味でアドバイスされたのだろうと思います。
    「RL78のミラー領域について」
    japan.renesasrulz.com/.../rl78
    今回の問題と関係ありそうです。

    CC-RLとGCCのMAPを比較してみました。

    CC-RLでのMAPファイルは次のようになりました。
    まず __farを使わないで const array[8K] を3つ使用した場合、.const セグメントに配置された
    .text 000000ce 00000127 5a 1
    .textf 00000128 000002a0 179 1
    .const 00003000 00008fff 6000 2 (24KB)


    const array[8K] を5つ使うと定数エリアオーバーのエラーを出すので、const __far long Array1[2048] = {1, 1,.... のように__far を付けて アドレッシングモードを変えることで対応しました。
    CC-RL const __far array[8K] を5つ使用し const領域が40KB では .constf セグメントに配置されました
    .constf セグメントはfar領域としてアドレッシングされるのだと思います。
    SECTION START END SIZE ALIGN
    .text 000000ce 00000127 5a 1
    .textf 00000128 00000327 200 1
    .const 00003000 00003000 0 2
    .constf 00003000 0000cfff a000 2 (40KB)


    次はGCCの場合
    GCCの場合は __far を付けなくてもエラーが出ないので、賢いコンパイラが自動時に farアドレッシングのセグメントに割り付けるのでしょうか?
    array[8K] を5つ使って const領域が40KB のMAPを見てみるとCC-RLとおなじアドレスに配置されましたが、.rodataセクションなので far領域ではないようです。
    .rodata 0x00003000 0xa00a ./src/r_main.o
    0x00003000 _Array1
    0x00005000 _Array2
    0x00007000 _Array3
    0x00009000 _Array4
    0x0000b000 _Array5
    *(.rodata.*)
    0x0000d00a _erodata = .
    .frodata 0x0000d00a 0x0


    GCCで __far を明示的に付けた場合のMAPです
    .frodataセクションに配置されました。
    .rodata 0x00003000 0xa
    *(.rodata)
    .rodata 0x00003000 0xa ./src/r_main.o
    *(.rodata.*)
    0x0000300a _erodata = .
    .frodata 0x0000300a 0xa000
    0x0000300a . = ALIGN (0x2)
    *(.frodata)
    .frodata 0x0000300a 0xa000 ./src/r_main.o
    0x0000300a _Array1
    0x0000500a _Array2
    0x0000700a _Array3
    0x0000900a _Array4
    0x0000b00a _Array5
    *(.frodata.*)
    0x0000d00a _efrodata = .


    GCCで __far を明示的に付けると配列定数は .frodata に配置され、定数の参照値も正しく暴走もしなくなりました。
    これで constの値を正しく参照できるようになった説明はできるけど、switch文の暴走まで止まりました。
    これはどういう理由でしょうか?
    そもそも今回、ちいさなプログラムコードが暴走した理由が分かりませんが、 配列定数の他に__near定数域に 0x000a(10バイト) が確保されています。

    ソースには現れない定数がswitch文で使われているようです(NoMayさんの言われた分岐テーブルですね)、そこでswich文の処理内で参照ミスがあって暴走するのかもしれません。
    相変わらず GCCを使うのが怖いです。

    定数を全て farセグメント域に追い込んで、nearの定数域を十分に空けておけばソースコードに現れない定数を安全にアクセスできて、全て解決できるのでしょうか?

    FAQになって当然のような問題なので、FAQで捜せなかったのが残念。というか初めっから敬遠されているコンパイラなの? まだ他にも地雷がありそうで怖いです。

  • これまでの調査で定数エリア大きい場合はミラー領域のサイズを越えないように、farアドレッシングのセクションに割り付ければプログラムの暴走を防止できると分かりましたが、
    私の元のプログラムは定数エリアのサイズがそれほど大きくないにもかかわらず暴走します。

    MAPファイルを見ると、プログラムコードの.textセクション が nearアドレッシング可能領域を埋めていました。
    .rodata のエリア開始アドレスが指定されているにも関わらず、そこが浸食されてメモリーマッピングで失敗していてもリンカーはワーニングを出してくれません。

    結局のところリンカースクリプトファイルを手直しで編集して、 nearアドレッシング可能領域を確保する必要がありました。
    リンカースクリプトファイル "linker_script.ld" は次のようになっています
    .textセクションは0番地より少し後ろからプログラムコードが配置され、やがて .rodata セクションを侵食します。
    リンカースクリプトファイル "linker_script.ld" を編集して .rodata セクションの記述を.text セクションより前に移すことで解決しました。
    GNURL78を使う場合は、リンカーと仲良くおつき合いできることがポイントかもしれません。


    変更前
    ..
    ..
    .text (. + __romdatacopysize):
    {
    *(.text)
    *(.text.*)
    etext = .;
    . = ALIGN(2);
    } > ROM
    PROVIDE(__rl78_abs__ = 0);
    .init :
    {
    *(.init)
    } > ROM
    .fini :
    {
    KEEP(*(.fini))
    } > ROM
    .got :
    {
    *(.got)
    *(.got.plt)
    } > ROM
    .rodata MAX(., 0x3000):
    {
    . = ALIGN(2);
    *(.rodata)
    *(.rodata.*)
    _erodata = .;
    } > ROM
    ..
    ..


    変更後
    ..
    ..
    .rodata 0x3000 : AT(0x03000)
    {
    . = ALIGN(2);
    KEEP(*(.rodata))
    KEEP(*(.rodata.*))
    . = ALIGN(2);
    } >ROM
    .text ( 0xB000 + __romdatacopysize):
    {
    *(.text)
    *(.text.*)
    etext = .;
    . = ALIGN(2);
    } > ROM
    ..
    ..

  • eokayamaさん、こんにちは。NoMaYです。

    GNURL78のリンカスクリプトの小技でconstとMirror領域の関係が適切かどうかチェック出来ることが分かりましたので、別スレッドにして後で投稿しようと思います。(リンカスクリプトが少し複雑になることと引き換えになりますが。)

    それで、eokayamaさんが変更されたリンカスクリプト(なるほど、.textによってMirror領域が侵食されていた、のですね)ですが、初期値あり変数のROM化処理に関することで、少し変えると良いかなと気付いたことがあったので、少し時間が経ってしまっていますがリプライします。

    実は、もともとのリンカスクリプトでは、以下の赤文字箇所の記述により、.torsセクションと.textセクションの間に、初期値あり変数の初期値が配置されるカラクリになっていました。(スタートアップルーチンでROMに配置された初期値をRAMにコピーするようになっていますが、そのROM側の初期値のことです。)

    もともとのリンカスクリプト

        .tors :
        {
            __CTOR_LIST__ = .;
            . = ALIGN(2);
            ___ctors = .;
            *(.ctors)
            ___ctors_end = .;
            __CTOR_END__ = .;
            __DTOR_LIST__ = .;
            ___dtors = .;
            *(.dtors)
            ___dtors_end = .;
            __DTOR_END__ = .;
            . = ALIGN(2);
            _mdata = .;
        } > ROM
        ←←← ここの間の部分に初期値あり変数の初期値が配置される仕組みだった
        .text (. + __romdatacopysize):
        {
            *(.text)
            *(.text.*)
            etext = .;
            . = ALIGN(2);
        } > ROM
        .data 0xF9F00: AT(_mdata)
        {
            . = ALIGN(2);
            _data = .;
            *(.data)
            *(.data.*)
            . = ALIGN(2);
            _edata = .;
        } > RAM
        PROVIDE(__romdatacopysize = SIZEOF(.data));

    eokayamaさんが変更されたリンカスクリプトでは、(問題無く動作するものの)そのカラクリの辻褄/理屈が合わなくなってしまっています。カラクリの辻褄/理屈を合わせるという観点からですが、以下のようにした方がベターかなと思います。(あと、これとは別の話になりますが、冗長な AT(0x03000) も無くせます。) なお、このセクション配置の場合、初期値あり変数の初期値は最大12Kバイト弱となりますが、それで足りなくなったら、再度、セクション配置の見直しが必要になります。(0x3000の下側(0番地側)を有効に活用するか、下側には収まらなくなったので諦めてスッパリと空けるか、Mirror領域というのは、なかなか悩ましいですね、、、)

    ベターかなと思うリンカスクリプト

        .tors :
        {
            __CTOR_LIST__ = .;
            . = ALIGN(2);
            ___ctors = .;
            *(.ctors)
            ___ctors_end = .;
            __CTOR_END__ = .;
            __DTOR_LIST__ = .;
            ___dtors = .;
            *(.dtors)
            ___dtors_end = .;
            __DTOR_END__ = .;
            . = ALIGN(2);
            _mdata = .; ← 削除
        } > ROM
        PROVIDE(__rl78_abs__ = 0);
        .init :
        {
            *(.init)
        } > ROM
        .fini :
        {
            KEEP(*(.fini))
        } > ROM
        .got :
        {
            *(.got)
            *(.got.plt)
        } > ROM
        _mdata = .;
        ←←← ここの間の部分に初期値あり変数の初期値が配置されるようになる
        .rodata 0x3000:
        {
            . = ALIGN(2);
            *(.rodata)
            *(.rodata.*)
            _erodata = .;
        } > ROM
        .text 0xB000:
        {
            *(.text)
            *(.text.*)
            etext = .;
            . = ALIGN(2);
        } > ROM
        .frodata :
        {
            . = ALIGN(2);
            *(.frodata)
            *(.frodata.*)
            _efrodata = .;
        } > ROM
        .data 0xF9F00: AT(_mdata)
        {
            . = ALIGN(2);
            _data = .;
            *(.data)
            *(.data.*)
            . = ALIGN(2);
            _edata = .;
        } > RAM
        PROVIDE(__romdatacopysize = SIZEOF(.data)); ← 不要ですが、再変更で必要になった時の為に残しました


    ちなみに、以下のセクションはC++プログラムで使用されるものなので、Cでは空のままです。(というのが私の理解ですが、そのことにより、eokayamaさんが変更されたリンカスクリプトのままでも(先程のカラクリの辻褄/理屈が合わなくなる件はありますが)問題無く動作します。)

    .tors セクション
    .init セクション
    .fini セクション
    .got セクション

  • NoMaYさん、リンカーのアドバイスありがとうございます。
    強引に?ミラー領域を開けたことで今回はメモリ配置できたけど、リンカースクリプトをちゃんと理解していないので問題も残ったようです。
    参考にさせて頂きます。
  • eokayamaさん、こんにちは。NoMaYです。

    今回の件が発端で、以下を投稿しました。(C++の話やリンカスクリプトエディタの話も入ってますが、、、) リンカスクリプトでASSERT()を利用してconst領域とMirror領域の関係が適切かどうかチェック出来るようにしてみました。(もう読まれたかも知れませんが、、、)

    GNURL78でconst領域/Mirror領域をちょっと安全に使えるようにlinker scriptのASSERT()で小技(TIPS)を考えてみた
    japan.renesasrulz.com/cafe_rene/f/forum21/5917/gnurl78-const-mirror-linker-script-assert-tips