いつもお世話になっております。
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混在
はし さん、こんにちは。NoMaYと申します。本件は、双方の話の行き違いを減らす為に、まずソースが見たいです。発端の話も含めて、以下のソースを見せて頂けませんか?(単に投稿に貼り付けるだけですとインデントがおかしくなって後の手間が増えますので、出来ればソースをzipファイルに固めたものをリプライに添付して頂けると有難いです。)(1) 以下のCソース(プロジェクトの全てのCソースということではありません)「far領域(00000H~017DFFH)のRomデータを取得して加算するプログラムをC言語で作成したのですが、010000H以上のアクセスができませんでした。」(2) 以下の呼び出される側のASMソースと呼び出す側のCソース(プロジェクトの全てのCソース/ASMソースということではありません)「16ビットアドレスのfar領域アクセス用関数(ES=0x00)と、20ビットアドレスのfar領域アクセス用関数(ES=0x01)を2つ用意して、共に引数をunsigned short アドレスとして、戻り値をunsigned charでデータとしました。」なお、咄嗟には、(2)に関して以下のように書かれている点で、Aレジスタに入れるのはCC-RLもしくLLVM-RL78でのルールだった筈で、GNURL78では違うルールだった筈で、そこからおかしくなっているように感じました。追ってデバッグして確認しますけれども。> 戻り値としてAレジスタにセットしてリターン
はし さん、こんにちは。NoMaYです。> Aレジスタに入れるのはCC-RLもしくLLVM-RL78でのルールだった筈で、GNURL78では違うルールだった筈で、ルールは以下のファイルに記載されています。これを読むと、やはり違いますね。(戻り値の返し方も、引数の渡し方も。)インストールフォルダ¥Doc¥RL78-ABI.html「…略…Arguments to functions are always passed on the stack, with the first argument in the parameter list pushed last. Note: this is the common "args on stack" layout most compilers use. 16-bit and larger types shall be 16-bit aligned. If an 8-bit type is passed between two 16-bit types, the 8-bit type shall be padded towards the LSB (i.e. towards address zero, so it ends up on an even address too).Return values shall be returned in R8..Rn for as many registers as are needed to store the value. That lets us store 8, 16, 32, and 64 bit values in the same place, without using up all the "real" registers (AX, BC, etc) that might be needed to compute the return value.…略…」[追記]かふぇルネでは、相変わらず、半角 64 が *** の伏字になってしまいますので、全角 64 に変えてあります。
NoMaYさま
ご回答いただきありがとうございます。
プログラムを整理していたら時間がかかってしまいました。
すぐに返答できず、申し訳ございません。
Cソースを添付いたします。
サム値を格納する領域をセクションを切って、const変数をそこに配置するようにしております。
そのconst変数のアドレスを用いて、00000H~const変数アドレスまで、ROMデータを取得して加算を考えております。
セクションの開始アドレスを017DFEHに設定したいのですが、00FFFEHまでしか正しく動きません。
const変数のアドレスを格納する変数をunsigned shortとしていることが原因だと思うのですが、unsigned longにするとよく分からない値が格納されてしまい、加算処理が途中で終了してしまいます。
昨日問い合わせ内容では、ESの記述が無く~と記載したのですが、今回プログラムを整理して確認したところ、ESの記述はちゃんとありました。申し訳ございません。
ES=0x1としたかったのですが、上記のカウント処理が上手く行かないことから、アセンブラに挑戦してみよう(カウンタで0x10000超えたらES=0x1セット)と考えて、質問いたしました。
もし、Cソースの方で解決策がありましたら、ご教授いただければ幸いです。
(質問と異なってしまいますが、、、)
アセンブラの件、ありがとうございました。
インストールフォルダ内にあることを失念しておりました(一生懸命ルネサスのホームページなどで検索しておりました)。
Aレジスタではないですね。
こちらも検討を進めたいと思います。
お手数をおかけしますが、宜しくお願いいたします。
1_C言語.zip
はし さん、こんにちは。NoMaYです。> const変数のアドレスを格納する変数をunsigned shortとしていることが原因だと思うのですが、まさに、その通りだと私も思います。
…略… unsigned long count; unsigned short sum_s = 0x00000L; unsigned short sum_e = (void*)&psum_rom; ← sum_eはunsigned short! …略…
…略… for ( count = sum_s ; count < sum_e ; count += 2 ) ← countがunsigned longでも、sum_eはunsigned shortですから。 …略…
> unsigned longにするとよく分からない値が格納されてしまい、加算処理が途中で終了してしまいます。思うに、ソースを整理する前の最初のソースが動作しなかったので、何とかしようと頑張っているうちに迷走?気味になっているような、そんな気がするのですが、、、すみません、最初のソースはもっと素朴なコードではなかったのでしょうかね?それとも、最初のものも頂いたソースと大差無い(といっても主観的なものですが)ものでしたか?もしも頂いたものとは違って素朴なものだったのでしたら、出来れば、そちらも見せて頂きたいのですが、、、
ご確認いただきありがとうございます。
ソース整理では、色々と関係ないものを排除したので、処理自体は変更していないです(ESレジスタについては再確認はしました)。
ただ、この処理にはベースのソフトがあります。
こちらは、バイト単位の加算処理で、prom自体をインクリメントしております。
また、セクションも番地指定をせずに、プログラムの最後尾にサム値を格納するものでした。
(たまたまプログラムの容量が小さかったので、16bitアドレス内で加算出来ればOKだったようです)
今回は、ワード単位の加算&プログラムの全領域をサムの対象とする必要があり、処理を変更し、デバッグをしておりました。
一応、ベースソフトを添付いたします(ただ、こちらも010000H以降の加算は不可です。)。
宜しくお願いいたします。
2_C言語(ベース).zip
はし さん、こんにちは。NoMaYです。それぞれソースをどうもありがとうございました。ベースのソースを見て(当方の話でしかないのですけれども)合点が行きました。また、頂いたソースを弄りながらビルドして何が起きているのかも見えて来たような気がします。高度に専門的な話になるのですが、GNURL78のPLTと呼ばれる機構にまつわる話のような気がしてきました。細かい話は後にした方が良いかも、とも思いつつ、、、実はGNURL78には関数farポインタが存在しません。(変数farポインタは存在します。) 関数farポインタが存在しないとなると0x10000以上に配置された関数を関数ポインタで呼び出すことが出来ないのではないか?という話になりますが、もともとGCCに備わっていたPLT(Procedure Link Table)という機構を流用/適用して、0x10000以上に配置された関数のアドレスが参照されると、0x10000未満の領域にスタブが自動的に作られて、そのスタブのアドレスが値として返されるような仕組みになっています。スタブといっても大したものではなくて、0x10000以上に配置された関数の本体の先頭へジャンプする分岐命令があるだけのものなのですが、スタブのアドレスは必ず0x10000未満になっていて、必ず関数nearポインタに格納可能なものになっているのです。問題は、どうも、その機構が以下の変数定義に対しても発動してしまっているようなのです、、、
const unsigned short psum_rom __attribute__ ((section (".psum"))) = 0x1234;
手元では、マップファイル上には以下のようにpsum_romに対してPLTに関するセクションが自動的に作られていました。
.lowtext 0x00000000000000d8 0x4 *(.plt) .plt 0x00000000000000d8 0x4 ./src/hashi_gnurl78_far.o ← ファイル名は気にしないで下さい 0x00000000000000da psum_rom.plt *(.lowtext) 0x00000000000000dc . = ALIGN (0x2)
推測ですが、このアドレスが以下の文面の「よく分からない値」の正体ではないかと思われます。(なお、今回、リンカスクリプトは頂かなかったので当方の手元のものとはアドレスが微妙に違うかも知れません。)> const変数のアドレスを格納する変数をunsigned shortとしていることが原因だと思うのですが、unsigned longにするとよく分からない値が格納されてしまい、加算処理が途中で終了してしまいます。対策(および少し当方の頭を冷やして再度の原因見直し)について、この後で考えてみようと思います。
はし さん、こんにちは。NoMaYです。しばらく五月雨式にリプライしますけれども(一気に解に辿り着けなさそうなので)、しばらく静観していて下さい、、、> const変数のアドレスを格納する変数をunsigned shortとしていることが原因だと思うのですが、unsigned longにするとよく分からない値が格納されてしまい、加算処理が途中で終了してしまいます。推測ですが、上の赤文字の話はコンパイル時のワーニングを無視していた?ことも関係すると思いますが、ワーニングを取って以下のようなソースにしても、それでもsum_eの値は意図したものになりません。続く。ソース
const unsigned short psum_rom __attribute__ ((section (".psum"))) = 0x1234;unsigned short _RomSum( void ){ const unsigned char __far *prom; unsigned long count; unsigned long sum_s = 0x00000L; unsigned long sum_e = (unsigned long)(const unsigned char __far *)&psum_rom; ← 0xf00d8になってしまう 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 );}
リンカスクリプト
…略… .psum 0x017DFE: AT(0x017DFE) { *(.psum) } > ROM …略…
マップファイル
…略….lowtext 0x00000000000000d8 0x4 *(.plt) .plt 0x00000000000000d8 0x4 ./src/hashi_gnurl78_far.o 0x00000000000000da psum_rom.plt *(.lowtext) 0x00000000000000dc . = ALIGN (0x2)…略….psum 0x0000000000017dfe 0x2 *(.psum) .psum 0x0000000000017dfe 0x2 ./src/hashi_gnurl78_far.o 0x0000000000017dfe psum_rom…略…
ただ、よくよく考えてみるとGNURL78のnear/farの世界では、以下の変数定義は__farが抜けているのでは?というのがあります、、、何か変?
上よりは望ましい気がする
const unsigned short __far psum_rom __attribute__ ((section (".psum"))) = 0x1234;
ただし、コンパイル時のリストファイルを見ると、なんとセクション名が意図したものではない、、、
169 .global _psum_rom 170 .section .froda,"a",@progbits 171 .balign 2 174 _psum_rom: 175 0000 34 12 .short 4660
続く。
対症療法探しの最中に気付いた、思わず笑ってしまったセクション名の不具合、ですが、なんで a がダブるのさ、、、ソース
const unsigned short __far psum_rom __attribute__ ((section (".frodata2.psum"))) = 0x1234;
リストファイル(なんで a がダブるのさ、、、)
169 .global _psum_rom 170 .section .frodaata2.psum,"a",@progbits 171 .balign 2 174 _psum_rom: 175 0000 34 12 .short 4660
対症療法探しの最中に気付いた、また別のセクション名の不具合(だと思うのだけれども)、ですが、なんで f が先頭に勝手に付くのさ、、、ソース(セクション名の先頭に f は付けていないのだけれども)
const unsigned short __far psum_rom __attribute__ ((section (".rodata2.psum"))) = 0x1234;
リストファイル(なんで f が先頭に勝手に付くのさ、、、)
169 .global _psum_rom 170 .section .frodata2.psum,"a",@progbits 171 .balign 2 174 _psum_rom: 175 0000 34 12 .short 4660
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-dereferencegcc.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