2012.4.4
前へ
次へ
ホームページトップへ戻る

復活!CP/M ワンボードマイコンでCP/Mを!
CP/MがTK−80互換のワンボードマイコンの上で復活します
ND80ZVとMYCPU80の上でCP/Mが走ります

[第81回]


●ファンクションコール06(E=FE)のデバッグ(2)

前回からの続きです。
ブレーク中にレジスタを書き換えるにはCR(チェンジレジスタ)コマンドを使います。
この操作はちょっとログファイルでは再現がむつかしいので、画面コピーで説明をすることにいたします。

CR[Enter]と入力すると、ブレークしたときと同じようにレジスタダンプが行なわれます。



一見すると全く同じように見えます。
上側がブレーク後のレジスタダンプで、下側がCRコマンドによるレジスタダンプです。
しかし、ブレークしたときのレジスタダンプと違って、CRコマンドによるレジスタダンプの場合には、表示後にプロンプトマーク(>)が表示されません。
カーソルマーク _ だけが表示されています。
CR(チェンジレジスタ)モードの実行中であることを示しています。

[↑][→]キーを使ってカーソルを変更したい値のところまで移動します。



今回はAレジスタの値を書き換えますから、Aレジスタの値20のところまでカーソルを移動します。

そこで数値を入力します。



’a’の文字コード61を入力しました。

書き換えが済んだら、そこで[Enter]を入力します。



下に変更後のレジスタダンプが表示されます。
今度はその下にプロンプトマーク>が表示され、CRモードを終了したことを示しています。

今回はAレジスタを書き換えましたが、他のレジスタも同じようにして変更することが可能です。
また同時に複数のレジスタを変更することもできます。
2つ以上のレジスタの値を変更するときは、1レジスタ毎に[Enter]を入力する必要はありません。
必要な変更を全て行なった後、最後に[Enter]を入力すれば、そのときに表示されているままの値が全て入力されます。
PC(プログラムカウンタ)やSP(スタックポインタ)も変更することができますが、不用意に変更すると、RT[Enter]を入力したときに暴走してしまうこともありますから、十分理解した上ですることが必要です。

さて、これでAレジスタの内容を変更して、文字’a’が入力されたことにしましたから、次のブレークポイントを設定します。
前回はアドレスD297でブレークしました(下にプログラムリストを再掲します)。

                    	;
                      	;CONSOLE STATUS
                      	;
  D28F  3E07          	CONSTJ:LD A,07
  D291  CDAB10        		CALL SOUT
  D294  CDAE10        		CALL SIN
  D297  FE20          		CP 20H
  D299  C29ED2        		JP NZ,CONSTJ2
  D29C  AF            		XOR A
  D29D  C9            		RET
  D29E  3207BB        	CONSTJ2:LD (CONSTDT),A
  D2A1  F601          		OR 01
  D2A3  C9            		RET
                      	;

ブレーク中にAレジスタの値を20から61に書き換えました。
D297ではAレジスタの内容と20Hが比較され、不一致ならばその次のJP NZ命令が実行され、一致すればパスされます。
そこで次のブレークポイントをD299に設定します。

>bp d299
>rt

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
6122 00FE D1FE C6D4 0044 0000 0000 0000 D299 C73F 0044 F1A1 FF 00100010

BP D299[Enter]で、次のブレークポイントをアドレスD299に設定しました。
RT[Enter]と入力すると、実行が再開され、直後に次のブレークポイント(アドレスD299)でブレークして、レジスタダンプが行なわれました。
PCには現在のプログラムカウンタの値が表示されています。
D299になっています。

D297のCP 20Hが実行された直後にブレークしましたから、Aレジスタの61Hと20Hが比較された結果、Z(ゼロ)フラグはオフ(0)になっています。

ということはこのあとD299のJP NZ,CONSTJ2が実行されますから、CONSTJ2(アドレスD29E)にジャンプするはずです。

ここでまずいことをしてしまったことに気が付きました。
そのあとのD2A1ではOR 01が実行され、その結果Aレジスタのビット0が1にされます。
しかしさきほどAレジスタの値を変更して設定した61Hはもともとビット0が1になっています。
61Hは01100001です。
せっかくのデバッグですから、そのようにAレジスタの値が書き換えられるところもお見せすることができるといいなあ、と思いました。

この時点ならまだ間に合いますから、Aレジスタの値を61Hから62Hに再変更することにしました。
62Hは01100010ですから、ビット0は0です。

さきほどと同じようにしてCRコマンドでAレジスタの値を61Hから62Hに書き換えました。



そうしておいて、次のブレークポイントをD2A3に設定します。
もう一度プログラムリストをお見せします。

                    	;
                      	;CONSOLE STATUS
                      	;
  D28F  3E07          	CONSTJ:LD A,07
  D291  CDAB10        		CALL SOUT
  D294  CDAE10        		CALL SIN
  D297  FE20          		CP 20H
  D299  C29ED2        		JP NZ,CONSTJ2
  D29C  AF            		XOR A
  D29D  C9            		RET
  D29E  3207BB        	CONSTJ2:LD (CONSTDT),A
  D2A1  F601          		OR 01
  D2A3  C9            		RET
                      	;

今はアドレスD299でブレークしています。
ゼロフラグがオフですからこのあとD299のJP NZ,CONSTJ2が実行され、CONSTJ2(アドレスD29E)にジャンプします。
そのあとの最後のRET命令のアドレスD2A3にブレークポイントを設定します。

>bp d2a3
>rt

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
6324 00FE D1FE C6D4 0044 0000 0000 0000 D2A3 C73F 0044 F1A1 FF 00100100
>cm bb07
BB07 62-

BP D2A3[Enter]で、次のブレークポイントをアドレスD2A3に設定しました。
RT[Enter]と入力すると、実行が再開され、次のブレークポイント(アドレスD2A3)でブレークして、レジスタダンプが行なわれました。
PCには現在のプログラムカウンタの値が表示されています。
D2A3になっています。
Aレジスタは、OR 01が実行された結果63になっています。
そのように変更される前の値は、LD (CONSTDT),Aの実行でCONSTDT(アドレスBB07)に保存されたはずです。
CM(チェンジメモリコマンド)でアドレスBB07の値を確認してみました。
CM BB07[Enter]です。
BB07の値は62になっています。

さて、ここから先なのですが。

●RET命令を実行したときの戻り先は?

今ブレークしているのは、アドレスD2A3です。
このあとはRET命令が実行されることになりますが…。

JP命令なら次にどこにいくのかがわかります。
しかしRET命令の場合、どこに戻るのか(どこからコールされたのか)がわからなければ、次のブレークポイントをどこに設定したらよいのかわかりません。

どうしたら、RET命令を実行したときの戻り先がわかるでしょうか?

それはRET命令の動作を理解すれば見えてきます。
RET命令の動作は、スタックから戻り先のアドレスをプログラムカウンタに戻して、そこにジャンプします。
これはCALL命令の動作と対になった動きです。
CALL命令は、その次のアドレス(つまりサブルーチンから戻ってくることになるアドレス)をスタックに入れて、指定先にジャンプします。
そこがCALL命令とJP命令の違いなのです。

もう、おわかりのことと思います。
そうです。
スタックを調べれば、戻り先がわかるのです。

レジスタダンプのSPを見てください。
スタックポインタです。
C73Fになっています。

あれえ?
でも、マシン語モニタのデバッグでも、スタックを使ってしまっているのではないの?
それじゃスタックの内容が変えられてしまっているのでは?

いいえ。
ブレークポイントでブレークしてユーザープログラム(ここではCP/Mシステムプログラム)からマシン語モニタに制御が移った直後に、スタックアドレスもマシン語モニタ用のスタックアドレスに切り換えて、その直前のCPUレジスタの全ての内容とともに、ユーザースタックの内容もそのまま保存するように、マシン語モニタはプログラムされているのです。
ですからブレークしたあとで、マシン語モニタのコマンドを使ってデバッグ作業を行なっても、ユーザープログラムのそれまでの実行環境は破壊されずにそのまま残っているのです。
よく考えてありますでしょう。

さて、それでは、そのスタックの内容を見てみることにいたします。
スタックはメモリに置かれていますから、DMコマンドを使います。
ダンプメモリです。
SP(スタックポインタ)はC73Fになっています。
そこでC73F前後のメモリ範囲として、アドレスC730からC74Fをメモリダンプしました。

DM C730,C74F[Enter]の実行です。

>dm c730,c74f
C730  10 A6 06 81 11 10 10 A6-06 81 11 FE 00 D4 C6 74  .ヲ.....ヲ.....ヤニt
C740  D1 00 00 FE D1 00 00 21-0B C4 5E 23 56 EB E9 0C  ム...ム..!.ト^#V...

現在のスタックポインタの値がC73Fですから、ここでRET命令が実行されると、C73FとC740の内容がPC(プログラムカウンタ)に入れられます。
C73Fは74、C740はD1です。
RET命令の実行後はアドレスD174にリターンするということがわかりました。

そこでD174に次のブレークポイントを設定することにします。


>bp d174
>rt

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
6324 00FE D1FE C6D4 0044 0000 0000 0000 D174 C741 0044 F1A1 FF 00100100
>cm d1de
D1DE 00-

BP D174[Enter]で、次のブレークポイントをアドレスD174に設定しました。
RT[Enter]と入力すると、実行が再開され、次のブレークポイント(アドレスD174)でブレークして、レジスタダンプが行なわれました。
PCには現在のプログラムカウンタの値が表示されています。
D174になっています。

ところで、D174にはどのようなプログラムが書いてあるのでしょうか?
アセンブルリストCPM22J.PRNの、その部分です。



Get here to return to the user.
というコメントがついています。

なるほど。
ここからユーザーのプログラムにリターンしていくわけです。

ところで。
アドレスD174の前(D173)の命令はCALL命令ではありません(RET命令)。
さきほどは、「CALL命令は、CALL命令の次のアドレスをリターン先アドレスとしてスタックにPUSHし、RET命令はそれをPOPしてプログラムカウンタにセットすることでリターン先に戻ってきます。」という意味の説明をしました。
しかしD174を見ると、その前にはCALL命令は無いことがわかります。

おそらく。
ファンクションコールのいろいろなルーチンは、CP/M内部ではCALL命令で呼び出されるのではなくて、JP命令を使った特殊な分岐で処理されていると思われます。
そしてユーザープログラムへの戻り先として、D174をスタックにPUSHしてから、それぞれのファンクションにジャンプしていると思われます。
それはたとえば、
LD HL,0D174H
PUSH HL
で実現できます。

それはそうとしまして、D174のプログラム内容は?

●コンソールステータスが得られなかった理由

なんと、思わず、あっと驚いてしまうようなプログラムでありました。

D174 3ADED1   LD A,(AUTO)

何をやっているのでしょう?
AUTOの内容をAレジスタに入れています。
Aレジスタ?

ちょっと待って。
Aレジスタには、コンソールステータスが入っていたはず…。
駄目じゃないの。
これじゃあ、Aレジスタにセットされている、せっかくのコンソールステータスの値が、破壊されてしまうじゃありませんか。

これで、やっとコンソールステータスが取得できないわけがわかりました。

ま。
しかし。
せっかくここまでブレークポイントを設定しながらデバッグしてきたことでもありますから、念の為に、この先まで進めてみることにいたします。

その前に。
AUTOの中味を確認しておくことにいたしましょう。
AUTOは、上のリストで見るとアドレスD1DEです。

>cm d1de
D1DE 00-
>bp d177
>rt

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
0024 00FE D1FE C6D4 0044 0000 0000 0000 D177 C741 0044 F1A1 FF 00100100
> 

CM D1DE{Enter]で確認をしました。
00が入っています。
むむ。
これがAレジスタに入れられてしまうので、いつも「キー入力無し」になってしまうのですね。

BP D177[Enter]で、次のブレークポイントをアドレスD177に設定しました。
RT[Enter]と入力すると、実行が再開され、次のブレークポイント(アドレスD177)でブレークして、レジスタダンプが行なわれました。
PCには現在のプログラムカウンタの値が表示されています。
D177になっています。
そしてAレジスタの値は、00になってしまいました。

これはおそらくCP/M2.2のバグだと思います。
地図を頼りにドライブしていたら、その先が突然池になっていた、ようなものです。
この問題は、CP/M 3 では解消されているようです。

ワンボードマイコンでCP/Mを![第81回]
2012.4.4upload

前へ
次へ
ホームページトップへ戻る