GAME-CCのやらかしバグについて
まず、問題のファイルは ランタイムパッケージに相当する 「gamecc.asm」の中の
W82 RMB 6+1 ; DIVIDE BINDEC/WORK
; FCB 0
この箇所、これ gamecc.asmの終わりの方の、ほとんどRMBの並んでる中、ただ1バイト分 0をメモリに書き込んでる。
そこだけピンポイントで0格納するようにHEXファイルが生成されれば無駄もないんだが、
アセンブラの種類によっては前後のRMBブロックもゼロフィルして無駄に大きなHEXファイルになる。
W82は6バイト確保だったのを7バイト確保にしてFCBを消したんだが、一見これでも 動作はする。
メモリというものが、電源入れた直後に全てゼロになる前提なら。今回のエミュはそうはいかない。
メモリの確保イコールゼロで初期化まではやっていない。実機でもSRAMにしろDRAMにしろ電源投入直後は電気的には不確定なのが通常で
ROMから起動したモニタがメモリをゼロフィルしてメモリの正常動作を確認するのが一般的な初期動作になる。
これまでにテストで使ったlinux上のエミュはメモリのゼロフィルが当たり前だったのが災いした形になる。
この6バイトは、数値の左詰め表示のための結果格納バッファで符号のための1バイト、1から32767までの5桁を入れる5バイトの合計6バイトになる。
そして、GAMEの内部処理では文字列は0終端の集合として扱って表示しているので、数値文字列の終わりのための0を入れる7バイト目が必要になる。
これを端折ったため、maze.hexのオブジェクトのエスケープシーケンスでカーソル位置指定する計算式で
結果が正しく出されずに謎文字列の羅列が爆発するバグにエミュレータ作成初期に悩まされた。
BYNDECルーチンのリターン直前に W82+7の位置に0を書き込むコードを挿入して回避するように修正。
エミュレータ側も、起動時にメモリのゼロフィルを入れて対応している。
すでに生成済みのオブジェクトを再アセンブルはしていないので気になる場合は各自で再アセンブルすれば
修正版のgamecc.asmを組み込んで修正版の実行オブジェクトが手に入る。コンパイラ自体には今のところ問題はないと思う。
次にExBasROMについて しめて5400行ちょいの アセンブラソースなんだが、その全ての行に丁寧なコメントが入ってる。
実に有り難いことだ、だが80系のアセンブラに慣れてしまった頭には
ラベルとOPコードの境目は「:」、コメントは 「;」で始める という 暗黙の掟みたいなのがある。
モトローラのアセンブラにはそれがほぼ無い。すべて スペースかTABで区切り 記述する位置で判別しているらしい。
それが全てのアセンブラに通用すればよいのだが、そうは行かない。
エミュレータ6809に内蔵のアセンブラはこのソースを無加工でアセンブルする。
しかも破綻なく、オリジナルのHEXファイルと同じバイナリになるレベルでできる。
エミュレータ内蔵のアセンブラで十分ではと? 思われるかもしれないが、
これも常用には不向きな難点がある例えば条件付きマクロなどが使えないとか、HEXファイルは出せてもBINは出せないとか
飽くまでも、エミュレータの補助機能だから過度な期待はしないほうが良い。
メインに使ってるasm6809はOPコード、オペランドの後ろの文字列をオペランドの延長の文字列と誤認識する。
OPコードだけで済むインヘレント命令に余計なオペランドがあるとエラーが大量に吐かれる。
またLABEL+4/2 なんて式を オペランドに記述すると 4/2を先に処理して、「LABEL+2」の結果がオペランドとして出される。
だから この手の式を見つけては(LABEL+4)/2のような形に変えてやる必要がある。
また、LDA LABEL のような場合、LABELがダイレクトページにあるかを判別しないので エクステンド命令に展開される。
ダイレクトページ内のバッファや変数を扱う命令に全てダイレクトだと指示する「>」を付加する必要がある。
第一段階はこの作業を、5400行全て見ながらやった。
答え合わせのできるHEXファイルが用意されてるので、
それと同じオブジェクトバイナリができるようにアセンブラソースを修正していけば良いだけ。
言葉としては簡単だが、地味で単調な作業を5400行分手作業でやるのはかなりキツイ。
答え合わせのやり方も、インテルHEXファイルもアセンブラ毎に表現に微妙に違いがあって、
単純にHEXファイルをdiffにかけて お終いとは行かない。
一旦 HEXファイルを BINファイルにして それをxxdでHEXダンプして 共通の形にしてからdiffにかける。
これを 延々と繰り返して 最終的には違うのはファイルネームだけって結果が出るまでやる。
ここまでが第一段階で 次からが ExBasROMのバグ取りの話になる。
DB7E 9F71 LA093 STX <TOPRAM ;SAVE ABSOLUTE TOP OF RAM
DB80 9F27 STX <MEMSIZ ;SAVE TOP OF STRING SPACE
DB82 9F23 STX <STRTAB ;SAVE START OF STRING VARIABLES
DB84 3089FF38 LEAX -200,X ;CLEAR 200 - DEFAULT STRING SPACE TO 200 BYTES
DB88 9F21 STX <FRETOP ;SAVE START OF STRING SPACE
DB8A 1F14 TFR X,S ;PUT STACK THERE
DB8C 8EDBD3 LDX #LA10D ;POINT X TO ROM SOURCE DATA
DB8F CE0076 LDU #LPTCFW ;POINT U TO RAM DESTINATION
DB92 C612 LDB #18 ;MOVE 18 BYTES
DB94 BDDCB3 JSR LA59A ;MOVE 18 BYTES FROM ROM TO RAM
DB97 CE00A7 LDU #IRQVEC ;POINT U TO NEXT RAM DESTINATION
DB9A C60E LDB #14 ;MOVE 4 MORE BYTES
;move 4 bug , move 14 ok? by nikohon
DB9C BDDCB3 JSR LA59A ;MOVE 4 BYTES FROM ROM TO RAM
DB9F 8639 LDA #$39
DBA1 97F0 STA <LINHDR-1 ;PUT RTS IN LINHDR-1
DBA3 BDDFC0 JSR LAD19 ;G0 DO A -NEW-
BASICの初期起動時に ROM上のリセットベクタやルーチンの一部をRAMエリアにコピーしているんだが
オリジナルのままだとDBE5からの4バイト転送になるが割り込みベクタ2件(JMP命令も含むのでこれだけで6バイト)だけでも 不足する。
乱数のシード値なども転送する必要があるようなので、全部で14バイト転送させることにした。
L907C PSHS A ;PUSH A CHARACTER ONTO THE STACK
LDA ,X+ ;GET NEXT CHARACTER FROM BUFFER
CMPA #'-' ;MINUS SIGN?
BEQ L907C ;YES
CMPA #'+' ;PLUS SIGN?
BEQ L907C ;YES
CMPA #'$' ;DOLLAR SIGN? ;$'$' ;DOLLAR SIGN?
BEQ L907C ;YES
CMPA #'0' ;ZERO?
BNE L909E ;NO - ERROR
LDA $01,X ;GET CHARACTER FOLLOWING ZERO
BSR L90AA ;CLEAR CARRY IF NUMERIC
BLO L909E ;BRANCH IF NOT A NUMERIC CHARACTER - ERROR
CMPA #'$'のところ 元は $'$' なんだが 前後の脈絡から イミディエイト命令に変更している。
この辺りはExBasROMにだけ残ってるUSING命令の部分らしく 滅多に使われないので 誰も気づいてないのかも?
ここまでが 元からのExBasROMのバグ修正で この後は 私がやった修正や機能追加などの話。
今回のエミュレータ作成の影響で気づいた点で、フルメモリ全域RAM状態の中でExBasROMを走らせると
状況によっては自分で自分を書き換えて破壊する可能性があること。
コールドスタートさせると まずRAMとして使える領域を探すためにあるポイントから前方にメモリの書き換えチェックを始める。
BASIC9のコールドスタート部分
BACDST LDX #PROGST+1 ;POINT X TO CLEAR 1ST 1K OF RAM
LA077 *
cmpx #1 ;
beq 0f ;0fはローカルラベル0を前方参照せよと指示している意味
CLR ,--X ;MOVE POINTER DOWN TWO-CLEAR BYTE
LEAX 1,X ;ADVANCE POINTER ONE
BNE LA077 ;KEEP GOING IF NOT AT BOTTOM OF PAGE 0
0 LDX #PROGST ;SET TO START OF PROGRAM SPACE
CLR ,X+ ;CLEAR 1ST BYTE OF BASIC PROGRAM
STX <TXTTAB ;BEGINNING OF BASIC PROGRAM
LA084 *
LDA 2,X ;LOOK FOR END OF MEMORY
COMA ;* COMPLEMENT IT AND PUT IT BACK
STA 2,X ;* INTO SYSTEM MEMORY
CMPA 2,X ;IS IT RAM?
BNE LA093 ;BRANCH IF NOT (ROM, BAD RAM OR NO RAM)
LEAX 1,X ;MOVE POINTER UP ONE
cmpx #ramend ;
bhs LA093
COM 1,X ;RE-COMPLEMENT TO RESTORE BYTE
BRA LA084 ;KEEP LOOKING FOR END OF RAM
ExBasROMのコールドスタート部分
BACDST LDX #PROGST+1 ;POINT X TO CLEAR 1ST 1K OF RAM
LA077 CLR ,--X ;MOVE POINTER DOWN TWO-CLEAR BYTE
LEAX 1,X ;ADVANCE POINTER ONE
BNE LA077 ;KEEP GOING IF NOT AT BOTTOM OF PAGE 0
LDX #PROGST ;SET TO START OF PROGRAM SPACE
CLR ,X+ ;CLEAR 1ST BYTE OF BASIC PROGRAM
STX <TXTTAB ;BEGINNING OF BASIC PROGRAM
LA084 LDA 2,X ;LOOK FOR END OF MEMORY
COMA ;* COMPLEMENT IT AND PUT IT BACK
STA 2,X ;* INTO SYSTEM MEMORY
CMPA 2,X ;IS IT RAM?
BNE LA093 ;BRANCH IF NOT (ROM, BAD RAM OR NO RAM)
LEAX 1,X ;MOVE POINTER UP ONE
cmpx #ramend
beq LA093
COM 1,X ;RE-COMPLEMENT TO RESTORE BYTE
BRA LA084 ;KEEP LOOKING FOR END OF RAM
LA093 STX <TOPRAM ;SAVE ABSOLUTE TOP OF RAM
STX <MEMSIZ ;SAVE TOP OF STRING SPACE
STX <STRTAB ;SAVE START OF STRING VARIABLES
LEAX -200,X ;CLEAR 200 - DEFAULT STRING SPACE TO 200 BYTES
STX <FRETOP ;SAVE START OF STRING SPACE
LA093 STX <TOPRAM ;SAVE ABSOLUTE TOP OF RAM
STX <MEMSIZ ;SAVE TOP OF STRING SPACE
STX <STRTAB ;SAVE START OF STRING VARIABLES
BASIC9では、前方探索でポインタが1になったら 次の段階に移るようになっているが
ExBasROMには それが無い ROMが載ったシステムなら それに反応して次の段階に移る。
だが、全域がRAMの場合 際限なく繰り返す。0を超えてマイナスになったときに切り上げるようだが
その時には 0xffffのリセットベクタの後半を破壊した後になる。
LEAX 1,X ;ADVANCE POINTER ONE
BNE LA077
のBNEはBRA としても 結果は同じになるかもしれない。(BRAに書き換えても 問題なく動作した確認済み)
その後、メモリを後方に向かって同様に書き換え可能か探索する。これもROMに到達して 書き換え不能まで繰り返される。
今回のケースだと、BASIC本体の格納エリアより前にACIAのポートがあるので
そこで書き換えた値と違うということでROMとみなされBASIC本体の破壊を免れている。
次に引っかかったのが、BASICが使えるメモリ残量が知りたくて PRINT MEM とかしたら、
OV error だか SN error だったかでまともに動作しない。基準にしてるオリジナルのHEXでは問題ない。
自分の今持ってる ExBasROMのソースに不備があるのは確か、徹底して調べて判ったのが2点
冒頭に出した、LABEL+4/2の形式のオペランドに含まれる算術式の解釈問題、
これによりコマンドや関数のジャンプベクタの引き出しが間違っていて見当違いのアドレスに飛んでいた。
もう一点は、
01EE STRBUF RMB 41 ;STRING BUFFER
これで、あきらかにDPの範囲外なのに、オペランドにSTRBUFがあるところで 「>STRBUF」としてた
当然、0x01eeじゃなく、0x00eeのメモリを参照してることになる。
これも奇跡的に 問題が表面化すること無く済んでて気が付かなかった。
最後の問題は、tek4010にグラフを表示させるプログラムをCTRL+Cで強制中断させた後に
キー入力ができなくなる問題に遭遇、よくよく見ると 改行が効かないだけで他は入力できている。
これは、BASICのPRINT文が持ってる問題で80文字くらいを連続で改行なしでprintしようとすると
BASICが勝手に0x0d,0x0aのコードを挿入する問題を抱えている。
tek4010のグラフ表示コードの最中に0x0d,0x0aを入れても 改行とは認識されず。グラフ表記構文の破綻を起こす。
BASICのソースに細工するとtek4010以外でのprint文に支障が出る可能性もあるので
エミュレータ側でこれを吸収することにしている。その影響で tek4010のグラフ表示中に中断すると
0x0d,0x0aを無視する状態のままで中断が入ると画面表示がおかしくなる。
そこで、BREAKのメッセージの頭に 0x1f(tek4010のグラフモード終了コード)を挿入した。
このコード自体は画面に出るコードじゃないので通常状態でも画面が乱れることはない。
異常の過程を経て、SBC6809改 程度の扱いのモノは形になったので 次回にでも アーカイブにして公開する予定。
その際に、付属のツール類の説明などもしようと考えている。今回扱った BASICのアセンブラソース類もそこに含める予定。
以前、GAME言語のあれこれについての中で、GAME80ICなどでZ80-MBC2とのscreenコマンドでのシリアル接続中にソーステキストのコピーペースト転送ができない事に触れていたが、単に使ってる環境に依存した所謂「おまかん」なのだが、回避したければ、素直にwindowsで使うか 若しくは linuxでもwine上でteratermを使えば済む話ではある。だが、我が家に於いては普段使いにできるwindows機など無い。棚の奥を探せばwin7の入った古PCが有るとは思うがボードマイコンとの接続の為だけに引っ張り出してきたいとまでは思わない。そこで ふと考えるにteratermにあってscreenやcu,minicomにないものはと、teratermには、テキスト転送中に行単位、文字単位でミリセカンドのディレイを挟む設定がある。対してlinux系のシリアル接続コマンドにそういった類のモノを知らない(単に自分が知らないだけの可能性もある) 。そこで、duckduckgo検索のサービスである「Duck.AI」に聞いてみた。自分の思いついた革新的なアイデアは大概は先人が既に思いついてる当たり前過ぎるモノというのはいつもの事であっさりと解決策を教えてくれた。
コメント
コメントを投稿