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

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

[第31回]

●Bdos Errの解明(2)

前回の続きです。
もう一度簡単にまとめてみます。

エラーメッセージが表示される直前のアドレスC4A5にブレークポイントを設定して、ブレークしたときのレジスタダンプは以下の通りでした。

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
F1B3 000F 805C C3EF F1B3 000F 805C C4D5 C4A5 C3A5 0044 F1A1 FF 10110011

もしもエラー表示ルーチンがどこかからCALLされたと考えれば、SP(スタックポインタ)の値を手がかりにして、スタックの中味を調べることによって、エラー表示ルーチンがどこからCALLされたかを知ることができます。

SPの値はC3A5になっています。
ここが現在のスタックポインタの最終アドレスです。
下はアドレスC3A5近辺のメモリの内容をダンプ表示したものです。

>d.,c390,c3af
C390  20 21 F0 C3 B6 C2 09 BE-C3 82 BF 00 00 00 00 96   !.テカツ.セテ.ソ.....
C3A0  BC 73 BE EF C3 0D C4 08-81 5F C3 00 00 24 24 24  シsセ.テ.ト.._テ..$$$

スタックは若いアドレスに向かって2バイトずつ消費されていきます。
スタックポインタは常に、最後に使われたスタックアドレスを保持しています。
C3A5の値は0Dです。これは下位バイトです。
次のC3A6はC4です。こちらは上位バイトです。
もしここがCALL命令の結果使われたのだとすれば、C40Dは、そのCALL命令の次のアドレスということになります。

アドレスC40Dは下のリストにあります。



このリストを見ると、C40Dにはプログラムの命令ではなくて、ただのデータ、ジャンプテーブルが置かれています。
本来は、命令としては実行されないアドレスです。
しかし…。

というところまでが前回のお話でした。

さて。
しかし。
このジャンプテーブルのコードをよくよく眺めてみましたら…。

ううむ…。
C409 99C4
C40B A5C4
C40D ABC4
と続く、このコードの並び…。

はて…?
おっ。
コードC4は、ひょっとして…!

Z80命令説明書をめくって調べてみました。
なんとコードC4は、CALL NZ命令でありました!

すると!
上のアドレスを下のように並べてみますと。

C406 C311C4  FBASE:JP FBASE1
C409 99      SBC A,C
C40A C4A5C4  CALL NZ,ERROR2
C40D ABC4

おお。
幻の、ERROR2へのCALL命令が浮かび上がってきたではありませんか!

ここまできて、やっと謎が解けました。
こういうことだったのです。

以下に整理してみます。

CP/Mをとりあえずは仮のRAMディスク仕様で走らせてみることにして、そのようにCP/Mプログラムを最小限手直しをしました。
そして、いくつかのテストプログラムを作って、それが正しく動作するかどうかを確認しました。
問題となったt1520.comもそのときに作って動作を確認したプログラムです。

ところがその後にCP/Mの先頭でJP命令がダブっていることに気が付き、それを削除しました。
その結果プログラム全体が前に3バイトシフトしてしまいました。
そのように変更したCP/Mを実行して、その上でt1520.comを実行したところ、BDOS Err…というエラーメッセージが表示されてしまいました。
調べてみたところ、そのエラーメッセージの表示ルーチンはアドレスC40AのCALL命令によってCALLされたらしいことがわかりました。
しかしC40AのCALL命令というのは、本来はCALL命令などではなくて、ただのデータ(ジャンプテーブルアドレス)だったはずです。
なぜ、本来はジャンプテーブルデータに過ぎなかったはずのコードが命令コードとして実行されてしまったのでしょうか?

その謎を解くカギは、t1520.comにありました。

2012/1/16  17:49  T1520.TXT
END=814A
              ; cp/m t1520 オウヨウcp/m p.82
              ;12/1/16
              ;
                ORG $8100
                FCB=$805C
                FCBCR=$807C
                DMABF=$8080
                DIRPOINT=$8790
                BDOS=$C409
              ;
8100 0E0F     START:LD C,0F;=15
8102 115C80     LD DE,FCB
8105 CD09C4     CALL BDOS
8108 FEFF       CP FF
810A CA3181     JP Z,OPNERR

アドレス8105に、CD09C4 CALL BDOS というCALL命令があります。
C409はBDOSのファンクションコールルーチンのエントリアドレスです。
下のリストを見てください。


このリストで、C406 C311C4  FBASE:JP FBASE1 がBDOSのファンクションコールのエントリアドレスです。
CP/Mの先頭のジャンプ命令を削除してしまったために、アドレスが3バイト前に移動して、C406になっていますが、ジャンプ命令を削除する前は、アドレスC409がファンクションコールのエントリアドレスだったのです。

もうおわかりのことと思います。
ファンクションコールのエントリアドレスがC409からC406に変更になったのに、アプリケーションプログラムではその変更に対応しないで、もとのままのC409をファンクションコールとして使ったために、アドレスC409がデータとしてではなく、命令として実行されてしまったのです。

C409にあるデータ99は命令コードではSBC A,Cという1バイト命令になります。
そこでその命令が無意味に実行されて、それからその次のアドレスC40Aが実行されたのです。

アドレスC40AのC4は命令コードではCALL NZという3バイト命令です。
ですから、3バイトがまとまって
C40A C4A5C4  CALL NZ,ERROR2
という命令として実行されることになります。

t1510.txtをもう一度見てみますと、アドレス8105のCALL BDOSの直前で、Cレジスタには0Fが入れられています。
このときAレジスタには何が入っているかは不明なのですが、結果から推測してみますと、おそらくこのプログラムの実行直前にはAレジスタはクリアされて00になっているようです。
アドレスC409の SBC A,C が実行された結果がさきほどのレジスタダンプに残っています。
もう一度お見せします。

A F  B C  D E  H L  A'F' B'C' D'E' H'L'  PC   SP   IX   IY  I  SZ H PNC
F1B3 000F 805C C3EF F1B3 000F 805C C4D5 C4A5 C3A5 0044 F1A1 FF 10110011

AレジスタはF1になっています。
Cレジスタは0Fですから、SBC A,Cは、
00−0F=F1という計算が行なわれた、と推測できます。
当然結果は0ではありませんから、Zフラグは立ちません。
つまりアドレスC40AのCALL NZ,ERROR2は、ERRO2ルーチンをコールしてしまったのです。

ちなみに、スタックアドレスのダンプリストをもう一度よく見てみますと

>d.,c390,c3af
C390  20 21 F0 C3 B6 C2 09 BE-C3 82 BF 00 00 00 00 96   !.テカツ.セテ.ソ.....
C3A0  BC 73 BE EF C3 0D C4 08-81 5F C3 00 00 24 24 24  シsセ.テ.ト.._テ..$$$

スタックポインタの値SP=C3A5には、C40AのCALL命令のリターン先である、その次のアドレスC40Dが入っていますが、その上のアドレスC3A7を見てみますと、そこには8108が入っています。

先ほどのt1520.comをもう一度見てみましょう。

2012/1/16  17:49  T1520.TXT
END=814A
              ; cp/m t1520 オウヨウcp/m p.82
              ;12/1/16
              ;
                ORG $8100
                FCB=$805C
                FCBCR=$807C
                DMABF=$8080
                DIRPOINT=$8790
                BDOS=$C409
              ;
8100 0E0F     START:LD C,0F;=15
8102 115C80     LD DE,FCB
8105 CD09C4     CALL BDOS
8108 FEFF       CP FF
810A CA3181     JP Z,OPNERR

8108は、8105のCALL BDOSを実行した後のリターン先アドレス(CALL命令の次のアドレス)であることがわかります。
このように、スタックを解析することでもプログラムの流れを調べることができるのです。

さてさて、これにて一件落着なのでありますが。
何も昨日今日にはじめてプログラムを書くようになったわけではなし、プログラムの途中を別のプログラムからCALLすると、今回のような危険に陥る可能性がある、ということは重々承知しておりました。
ですから、他からCALLされることが前提のプログラムを書くときは、必ずプログラムの先頭部分に、その中にあるサブルーチンへのジャンプ命令を書くことにしています。

ところで、CP/Mでは、問題のファンクションコールのエントリアドレスFBASEへのジャンプ命令は、RAMエリアの先頭部分のアドレス0005Hに書かれることになっています。
テストとしてとりあえず修正して作ったRAMディスク用のCP/Mは、まだメモリ増設基板は取り付けないままでテストをしますから、FBASEへのジャンプ命令は8005Hに置くことになります。

しかしそうすると、CP/Mを起動する前に、8005Hにジャンプ命令を入れるプログラムを書いておいて実行しなければなりません。
まだそこまでする前のテストをしようとする段階でしたから、手っ取り早く禁断の途中アドレスCALLをやってしまったのです。
なに、途中のアドレスを変えないように注意しながらCP/Mを直していくから、全然オーケーね。ノープロブレムだよお。

ええ。
そのように自分に言い聞かせながら慎重に作業をしていたのでありましたが。

記憶力全くゼロという自らの偉大なる能力を、これまた完全に失念しておりましたゆえ、わずか1週間の空白の後に見事に全てを忘れてしまい、堂々とタブーを犯してしまったのでありました。
まことにも人生とは、日々後悔の連続なのであります(恥&涙)。

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

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