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

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

[第30回]

●Bdos Errの解明

前回からの続きです。
そこしか通らないはずのアドレスにブレークポイントを設定したにもかかわらず、そのアドレスをすり抜けてエラーメッセージが表示されてしまいました。
プログラム的には有り得ないことが、現実に起きています。
ということは、本来の設計を離れたルートを通ってエラー表示ルーチンが実行された、ということになります。

そういうことならば。
事実は事実として認めた上で、それではどういうルートでエラー表示ルーチンが実行されたのか、ということを追及していくことにいたします。

前回は、D035にブレークポイントを設定してトラップを張ったのですが、見事にすり抜けられてしまいました。

D035 CC47C7   CALL Z,SLCTERR

は、通らなかったということです。
それならば、理由は置くとして、その先のSLCTERRが実行されてしまった可能性はあるのでしょうか?

SLCTERRは前回お見せした、こちらのリストにあります。



SLCTERRのアドレスはC747ですが、その次のC74Aも必ず実行されるはずですから、C74Aにブレークポイントを設定して、前回と同じように、CP/Mを起動して、DIRコマンドを実行したあと、t1520 t1520−2.txt を実行してみました。

その結果は、下のログファイルにありますように、やはりC74Aに設定したブレークポイントを通らずにエラーメッセージが表示されてしまいました。

>/ld cpm_1o4.bin,bc00
loading CPM_1O4.BIN ...16e8(5864)bytes loaded,from BC00 to D2E7
>bp c74a
>jp bc00

a>dir
A: FILLE5   COM : ABC      COM : CPMTST9  COM : TST012   COM
A: TST6     COM : TEST1520 TXT : T1520    COM : T1520-2  TXT
a>t1520 t1520-2.txt

Bdos Err On A: Select

どうやらもっと先の部分がいきなり実行されてしまっているようです。
もう、何かミステリーの世界にでも突然スリップしてしまったような感じです。
C747 210BC4  SLCTERR:LD HL,BADSLCT
C74A 5E      JUMPHL:LD E,(HL)
の先ということになりますと、BADSLCTをたぐっていくことになります。

BADSLCTも前回お見せしたこちらのリストにあります。



しかしBADSLCTにありますのはジャンプテーブルで実行命令ではありません。
C40B A5C4 BADSLCT:DEFW ERROR2
は、ここに書かれているC4A5がHLレジスタに入れられて、JP (HL)命令でそのアドレス(C4A5)にジャンプするためのテーブルです。
つまりは、そのテーブルに依らないで、いきなりC4A5が実行されたという推測が成り立ちます。

むむ。
なんと、まるで非常線の警戒網を見事にくぐりぬけた犯人の残した足跡をたどる探偵の心境そのものですなあ。
はたして犯人はいかにしてこの厳しい警戒をくぐりぬけて目的を達成したのでありましょうや?

現場百遍でありますから、どうしてもC4A5に行かざるを得ませんでありましょう。

C4A5も前回お見せしたこちらのリストにあります。
なんだか前回たどった道を逆に歩いているような気がいたします。



おや、ここは?
前回たどりはじめた最初のポイントです。
C4A5 21D5C4 ERROR2:LD HL,BADSEL
ここは、あのエラーメッセージを表示する、一番コアな部分のルーチンです。
いくらなんでもここを外しては、あのエラーメッセージを表示することは不可能でありましょう。

ま、しかし、ERROR2はHLレジスタにBADSELをセットしてERROR5にジャンプしていますから、ERROR5にトラップを張っても捕まえることはできるはずです。
ERROR5はアドレスC4B4ですから、そこにブレークポイントを仕掛けて、犯人がひっかかるのを待つことにいたしました。

>/ld cpm_1o4.bin,bc00
loading CPM_1O4.BIN ...16e8(5864)bytes loaded,from BC00 to D2E7
>bp c4b4
>jp bc00

a>dir
A: FILLE5   COM : ABC      COM : CPMTST9  COM : TST012   COM
A: TST6     COM : TEST1520 TXT : T1520    COM : T1520-2  TXT
a>t1520 t1520-2.txt

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 C4D5 0044 8000 0000 800D C4B4 C3A5 0044 F1A1 FF 10110011

おお。
ついに、捕まえました。
今度こそ、エラーが表示される直前に、逮捕です。
理由はまだわかりませんが、今までたぐってきたルートとは別のルートを通って、ここに来ていることはほぼ間違いありません。

ですが、念の為。
今回最後に狙いを定めた、ERROR2にトラップを仕掛けて、そこでもひっかかるかどうかを試してみました。
ERROR2はアドレスC4A5ですから、そこにブレークポイントを設定します。
その結果は。

>bp c4a5
>jp bc00

a>dir
A: FILLE5   COM : ABC      COM : CPMTST9  COM : TST012   COM
A: TST6     COM : TEST1520 TXT : T1520    COM : T1520-2  TXT
a>t1520 t1520-2.txt

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

やっぱり、トラップにひっかかりました。
もう間違いありません。
本来通ることのない、謎の道を通って、このERROR2が実行されたことに間違いはありません。

しかし。
そうなると、次なる問題は、一体どこを通って、ERROR2が実行されたか?
ということです。

こういうことになりますと、デジタルアナライザでもつかうか、もっと高度なデバッガを使って、最初から自動トレースするしかないのでは?
などとお考えになるかもしれません。

いえいえ。
まだまだ、そんなものは不要であります。
まだギブアップしなくても、捜査の方法は残されておりまする。

犯人は、どこを通って、ここに侵入したのか?
きっとその手がかりは、現場に残されているはずである。

しかり。
その手がかりはここに、残されていたのであります。

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

アドレスC4A5でブレークしたときに表示されたレジスタダンプです。
これは、CPUがアドレスC4A5の命令を実行する直前のCPUレジスタの内容を表示したものです。
いわばCPUの軌跡の最後の瞬間を捕らえたものだということになります。
ここに、CPUがどこからやってきたのかが示されているのです。

いったい、どこに、そんなものが?

SPを見てください。
SPはスタックポインタ(Stack Pointer)です。
ここにCPUの軌跡があります。
SPの値はC3A5になっています。
ここが現在のスタックポインタの最終アドレスです。

スタックはレジスタをPUSH命令で保存するときにも使われますが、サブルーチンをCALLしたときに、戻り先(つまりCALL命令の次のアドレス)を保存するためにも使われます。
おそらくはERROR2がどこかからCALLされたに違いない、と考えたとすれば、それならスタックを調べれば、どこからCALLされたのかがわかる、はずです。

現在のスタックポインタの値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の前にはCALL命令が書かれているのでありましょうか?
実はアドレスC40D近くのプログラムは、今回の始めの方でお見せしたリストについています。
しかしいよいよ核心に迫る、肝心のところでありますから、もう一度下にお見せすることに致します。



C40Dを見ていただきますと…。
こ、ここは…。
命令ではない…!?

C40Dも、その前もCPUが実行すべき命令ではなくて、HLレジスタに入れるジャンプ先アドレスデータです。
マシン語に詳しくない方はよくおわかりにならないかも知れませんが、CPUは必ずしも全てのコードを命令として実行するとは限らないのです。
プログラムの流れの中で、ただの文字コードとしか認識しない、ということも有り得ます。
そうでなければCPUが「文字」を扱うことはできませんから。

で。
ここはプログラムとして、つまりCPUが実行する命令としてあるのではなくて、ただのデータ、ジャンプテーブルとして存在しているのです。
すると。
スタックに保存されたC40Dは、推理してきたようにCALL命令のリターン先アドレスなどではなくて、このテーブルアドレスがたとえばHLレジスタに入れられていたとして、それがPUSH HL命令でスタックに入れられたものでしかなかったのか?

しかし。
C40Dをテーブルアドレスとして見ると、そこは
RODISK:DEFW ERROR3 ;disk is read only.
という全く今までに出現したことのないエラーメッセージを表示するためのテーブルアドレスですから、その可能性はかなり少ないと考えられます。

うーん。
ということになりますと…。
やっぱり、ここは、命令として、実行されたのか?

いよいよ謎解きの最終章なのでありますが、本日はここまでといたします。
謎解きのためのヒントといたしまして、t1520.comの最初のあたりのリストを以下に示しておくことにいたします。
このリストは[第15回]でお見せしたものです。

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

さて。
もともとは正しく動いていたものが、CP/Mの先頭のJP命令がダブっていることに気が付いて、それを削除してしまったところ(その結果それ以降のプログラムアドレスがすべて3バイト前方にシフトしてしまったのでありますが)、突然にBdos Errが表示されるようになってしまいました。
前回と今回とで、なぜそのようなことが起きてしまったのか(おそらくは先頭の3バイトを削除したことに起因していると思われるのでありますが)、その理由について追求してまいりました。

そして。
お話はついにその核心部分に到達したのであります。
聡明なる読者諸賢におかれましては、すでに事態の全容を把握せられたことと思います。

次回解決編。乞うご期待。

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

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