「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分岐数を減らすと暴走しなくなります。(逆アセンブラのコードもかなり変化します)

Parents
  • 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 セクション

Reply
  • 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 セクション

Children
No Data