C言語からのアセンブラ関数呼び出しについて

いつもお世話になっております。

far領域(00000H~017DFFH)のRomデータを取得して加算するプログラムをC言語で作成したのですが、010000H以上のアクセスができませんでした。逆アセンブリをみたところ、ESレジスタの記述が見つからず、そこで、データ取得処理をアセンブラで記述してみました。

アセンブラ関数は、

16ビットアドレスのfar領域アクセス用関数(ES=0x00)と、20ビットアドレスのfar領域アクセス用関数(ES=0x01)を

2つ用意して、共に引数をunsigned short アドレスとして、戻り値をunsigned charでデータとしました。

Cソースファイルからアセンブラ関数を呼び出して、指定アドレスのデータを取得したいのですが、取得出来ません。

ステップ実行で確認すると、アセンブラ関数では指定のアドレスデータを取得できて、戻り値としてAレジスタにセットしてリターンしているのですが、C側に戻ると取得データは格納されておらず、逆アセンブリを見てみると、別のアドレスを指していました。

C言語からアセンブラ関数を呼び出す際の注意点などありましたら、教えていただきたく、宜しくお願いいたします。

【環境】

 マイコン  :RL78/G13(S2コア)

 開発環境  :e2studio ver 2022-04 (22.4.0)

 コンパイラ :GCC for Renesas RL78 ver 4.9.2.202201  最適化 -o1

 プロジェクト:C++、C混在

  • const __farで修飾された変数定義で__attribute__((section("セクション名")))でセクション名を変更するのは諦め、先日別スレッドで知ったリンカスクリプトの小技を使ってみました。(もっとも、そこは瑣末の部分で、本質的な部分はconstでは無くconst __farで修飾するように変更したこと、ですけれども。) そして、sum_eの値を確認すると期待した通りの0x017dfeになりました。まだ続きます。

    関連リンク

    GCCプロジェクトで変数や関数を指定のセクションに配置する方法
    japan.renesasrulz.com/cafe_rene/forums-groups/mcu-mpu/rx/f/forum5/7937/gcc

    ソース

    const unsigned short __far psum_rom = 0x1234; /* This variable is placed at the end of the ROM by linker script. */

    unsigned short _RomSum( void )
    {
        const   unsigned char   __far   *prom;
                unsigned long   count;
                unsigned long   sum_s = 0x00000L;
                unsigned long   sum_e = (unsigned long)&psum_rom; ← 期待した通りの0x017dfeになる(ちなみに強制的キャストを1つ減らせました)
                unsigned char   rom_h, rom_l;
                unsigned short  sum = 0;

        // 1Word単位で加算
        prom = (const unsigned char __far *)sum_s;
        for ( count = sum_s ; count < sum_e ; count += 2 )
        {
            rom_h = *prom;
            ++prom;
            rom_l = *prom;
            ++prom;

            sum += (unsigned short)(((unsigned short)rom_h << 8) | rom_l);
        }

        return( sum );
    }

     
    リンカスクリプト

        …略…
        .frodata :
        {
            . = ALIGN(2);
            *(.frodata)
            *(EXCLUDE_FILE(*/hashi_gnurl78_far.o) .frodata.*)
            _efrodata = .;
        } > ROM
        …略…
        .psum 0x017DFE: AT(0x017DFE)
        {
            */hashi_gnurl78_far.o(.frodata.*)
        } > ROM
        …略…

     
    マップファイル

    …略…
    .lowtext        0x00000000000000d8        0x0
     *(.plt)
     .plt           0x00000000000000d8        0x0 ./src/hashi_gnurl78_far.o ← 今回は中身は何もありません
     *(.lowtext)
                    0x00000000000000d8                . = ALIGN (0x2)

    …略…
    .frodata        0x00000000000004d2        0x0
                    0x00000000000004d2                . = ALIGN (0x2)
     *(.frodata)
     *(EXCLUDE_FILE(*/hashi_gnurl78_far.o) .frodata.*)
                    0x00000000000004d2                _efrodata = .
    …略…
    .psum           0x0000000000017dfe        0x2
     */hashi_gnurl78_far.o(.frodata.*)
     .frodata.psum_rom
                    0x0000000000017dfe        0x2 ./src/hashi_gnurl78_far.o ← ちなみに0x1234が入っています
                    0x0000000000017dfe                psum_rom
    …略…

     

  • ワーニングに気を付けていれば気が付けるだろうかなぁ、と考えを巡らせていたのですけれども、当方でソースを整理した以下のソースでも、const __farで修飾された変数定義をconstだけの修飾にしても(e2 studioで設定出来る限りのワーニング検出を有効にしていても)何もワーニングは出ることもなく誤動作するようになってしまいますので(PLTのスタブのアドレスが値として返されてしまってsum_eが期待した値になりませんので)、何と言うか、気を付けましょう、の精神論ぐらいしか思い浮かびませんでした、、、

    #include <stdint.h>
    const uint16_t __far psum_rom = 0x1234; /* This variable is placed at the end of the ROM by linker script. */
    uint16_t _RomSum(void);
    uint16_t _RomSum(void)
    {
        // FIXME: If one of -O2, -O3, -Os is used, the generated code calls `abort()` immediately.
        // FIXME: In case of using the immediate value in stead of `&psum_rom`, if other than -O0, -Og is
        // used, the generated code has no ES register handling. (Note: If one of -O2, -O3, -Os is used,
        // the generated code calls `abort()` immediately. Therefore `other than -O0, -Og` means only -O1.)

        const uint16_t __far *prom, *sum_s, *sum_e;
        uint16_t sum = 0;

        sum_s = 0;
        sum_e = &psum_rom; // (const uint16_t __far *)0x017DFEUL; --> No ES register handling.
        for( prom = sum_s; prom < sum_e; prom++ )
        {
            sum += *prom;
        }
        return sum;
    }

     
    続く。

  • 少し脱線しますが、最適化オプションで-O2, -O3, -Osを指定したところ、ソースのプログラムがNULL領域をアクセスすることをGNURL78がコンパイル時に検出して、ソッコーでabort()を呼び出す以下のコードが生成されましたね、、、(Linux/Windows/MacOS/その他もろもろ、といった汎用OS向けのGCCの機能として存在することは耳にしていましたが、いざ自分が遭遇すると、組み込み向けでは勘弁して欲しいなぁ、といったところですかね、、、(少なくとも0番地側にフラッシュメモリを配置するタイプのマイコンでは、、、))

      32                                .text
      34                                    .global _abort
      35                                    .section    .text._RomSum,"ax",@progbits
      36                                    .global __RomSum
      38                                __RomSum:
      44 0000 11 AF 00 00                   movw    ax, es:!0
      45 0004 FC 00 00 00                   call    !!_abort

     
    続く。

  • また少し脱線しますが、ソースを以下の通りに変更するとESレジスタを操作していないコードが生成されますね、、、

    ソース変更箇所

        sum_e = (const uint16_t __far *)0x017DFEUL;

     
    リストファイル(最適化オプションは-O1です)

      32                                .text
      34                                    .section    .text._RomSum,"ax",@progbits
      35                                    .global __RomSum
      37                                __RomSum:
      41                                    ; start of function
      42                                    ; push 2: r16
      43                                    ; uses ES register
      44 0000 61 EF                         sel rb2
      45 0002 C1                            push    ax ; r16
      47 0003 61 CF                         sel rb0
      50 0005 C9 F4 00 00                   movw    r12, #0
      51 0009 C9 F6 00 00                   movw    r14, #0
      53 000d C9 F0 00 00                   movw    r8, #0
      55                                .L2:
      57 0011 AD F4                         movw    ax, r12
      58 0013 BD F2                         movw    r10, ax
      59 0015 FA F2                         movw    hl, r10
      60 0017 AB                            movw    ax, [hl]
      61 0018 06 F0                         addw    ax, r8
      62 001a BD F0                         movw    r8, ax
      *** 001c AD F4                         movw ax, r12
      65 001e 04 02 00                      addw ax, #2
      66 0021 BD F4                         movw r12, ax
      67 0023 AD F6                         movw ax, r14
      68 0025 61 D8                         sknc
      69 0027 A1                            incw ax
      70 0028 BD F6                         movw r14,ax
      73 002a AD F6                         movw    ax, r14
      74 002c 44 01 00                       cmpw   ax, #1
      75 002f AD F4                          movw   ax, r12
      76 0031 61 F8                          sknz
      77 0033 44 FE 7D                       cmpw   ax, #32254
      78 0036 61 E8                          skz
      79 0038 EC 11 00 00                    br !!.L2
      81 003c 61 EF                         sel rb2
      82 003e C0                            pop ax ; r16
      83 003f 61 CF                         sel rb0
      84 0041 D7                            ret


    続く。
     

  • > 最適化オプションで-O2, -O3, -Osを指定したところ、ソースのプログラムがNULL領域をアクセスすることをGNURL78がコンパイル時に検出して、ソッコーでabort()を呼び出す以下のコードが生成されました

    GCCのドキュメントの最適化オプションの項を読んだところ、以下のようにソースを記述すれば対処出来ることに気付きました。(ベストなやり方では無いかも知れませんけれども。) ただ、e2 studioでは、エディタ上で文法エラーとされてしまって、関数全体に渡ってエラーの波線が表示されるようになって、ちょっとうっとうしくなってしまいますけれども。(GCCの拡張機能のごく普通に使われるものを使っただけだったのですが、e2 studioでそうなってしまったことがちょっと意外でしたけれども。)

    #include <stdint.h>
    const uint16_t __far psum_rom = 0x1234; /* This variable is placed at the end of the ROM by linker script. */
    uint16_t _RomSum(void);
    uint16_t __attribute__((noinline, optimize("no-isolate-erroneous-paths-dereference"))) _RomSum(void)
    {
        // FIXME: In case of using the immediate value in stead of `&psum_rom`, if other than -O0 is used,
        // the generated code has no ES register handling. It seems to be a bug of GNURL78 (ex. 4.9.2.202201).

        const uint16_t __far *prom, *sum_s, *sum_e;
        uint16_t sum = 0;

        sum_s = 0;
        sum_e = &psum_rom; // (const uint16_t __far *)0x017DFEUL; --> No ES register handling.
        for( prom = sum_s; prom < sum_e; prom++ )
        {
            sum += *prom;
        }
        return sum;
    }

     
    続く。

    [関連リンク]

    -fisolate-erroneous-paths-dereference
    -fno-isolate-erroneous-paths-dereference
    gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fisolate-erroneous-paths-dereference

    __attribute__ ((optimize (string, …)))
    gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-optimize-function-attribute

    __attribute__ ((noinline))
    gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-noinline-function-attribute
     

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

    ふと最初の投稿の質問/相談を見返して思ったのですけれども、以下の生成コード異常に気付いた時点で、かふぇルネに投稿されるのもひとつの手だったのかも知れないなぁ、という気もしました。すみません、五月雨式にとりとめもなくリプライを続けていましたが、ここでひと区切りにしたいと思います。

    > 逆アセンブリをみたところ、ESレジスタの記述が見つからず、

    はしさんの手元での時系列は分からないですけれども、あるいは、以下の時点というのも別のひとつの手だったのかも知れないなぁ、とも思いました。

    > 変数をunsigned shortとしていることが原因だと思うのですが、unsigned longにするとよく分からない値が格納されてしまい、

  • NoMaYさま

    ご対応いただき誠にありがとうございます。

    一昨日ご投稿いただいた内容で

    > 推測ですが、このアドレスが以下の文面の「よく分からない値」の正体ではないかと思われます。(なお、今回、リンカスクリプトは頂かなかったので当方の手元のものとはアドレスが微妙に違うかも知れません。)

    まさに、この".plt   0x00000000000000d8"の値が入っていました。

    > 推測ですが、上の赤文字の話はコンパイル時のワーニングを無視していた?ことも関係すると思いますが、ワーニングを取って以下のようなソースにしても、それでもsum_eの値は意図したものになりません。続く。

    はい。ワーニングが出ても仕方がないという感じで無視しておりました。

    ふと最初の投稿の質問/相談を見返して思ったのですけれども、以下の生成コード異常に気付いた時点で、かふぇルネに投稿されるのもひとつの手だったのかも知れないなぁ、という気もしました。すみません、五月雨式にとりとめもなくリプライを続けていましたが、ここでひと区切りにしたいと思います。

    元々が一応動作していたソフトだったので、自分が何かやらかしていると思い、ガチャガチャやってしまったのがまずかったです。

    少しずつ整理して検討して、分からないと思ったところで、かふぇルネに投稿すればよかったと思っております。

  • はしさん

    中の人です。既に解決済みでしたらすみません。

    GCCの開発関係者に確認してみました。以下のようにソースを修正することで正しく動くはずという意見をもらえました。

                                                 const unsigned short __far psum_rom     __attribute__ ((section (".psum"))) = 0x1234;

     

                                                 unsigned long    sum_e = (void __far *)&psum_rom;

    ご確認頂ければと思います。

    なお、別件のセクション名の問題は不具合と思われるとのことです。

  • adu様

    ご回答いただきありがとうございます。

    上記内容で試してみたのですが、psum_romが0xDE番地(PLTに関するセクション)に配置されてしまい、forの繰り返し数が222回となってしまいました。

    上記対策は、NoMaYさまに教えていただいた、PLTに配置しない方法をやったうえでの対策ということでしょうか??

  • いえ、その方法とは関係なく提示させていただきました。

    しかし流れをすべて追い切れていないかもしれず、混乱してきました。zipファイルが2種類あるのに気づいておらず、現状のはしさんの困りごとが何なのかもわからなくなってきてしまいました。

    現状すでに解決済みでしたら是非無視してください。