MC6800 ROMモニタ自作 その11 〜D改善、SWIブレーク、簡易逆アセンブルを入れる〜

前回は、MIKBUG 互換の固定入口を足して、SBC6800 データパックの HELLO と MICBAS13 を動かしました。これで、ROMモニタとしては「ロードして実行する」ところまではかなり使えるようになりました。
ただ、実機で小さいプログラムを試していると、次に欲しくなるのは「もう少し中を見たい」という機能です。メモリを見たい。範囲を指定して見たい。途中で止めたい。止まったときのレジスタを見たい。ついでに、今そこにあるコードを少し逆アセンブルして見たい。
今回はそのあたりをまとめて入れました。
今回の追加要件を D 改善、SWIブレーク、簡易逆アセンブルに分けて進めた
今回やりたかったこと
今回やりたかったのは、ROMモニタを「実行するだけ」から「少し追える」方向へ進めることです。
Dコマンドで範囲指定できるようにする- RAM 上のプログラムに SWI ブレークポイントを置けるようにする
- ブレークで止まったときにレジスタを表示する
- 簡易逆アセンブル
Uコマンドを入れる
ついでに、使い方を忘れないように、コマンドリファレンスも作りました。要件書にコマンド一覧はあったのですが、実際に端末からどう打つか、何が出るか、という利用者向けの文書はまだなかったので、ここも今回まとめました。
D コマンドを範囲指定できるようにした
まずは D コマンドです。今までもメモリダンプはできましたが、開始アドレスから見るだけでした。実機で確認していると、「この 32 バイトだけ見たい」とか「前回の続きからもう 64 バイト見たい」という場面がけっこうあります。
`D
D0100
D0100-011F`
D は継続アドレスから 64 バイト表示します。D0100 は $0100 から 64 バイト表示します。D0100-011F は $0100 から $011F までを表示します。終了アドレスは inclusive、つまり表示範囲に含めます。
D0100 と範囲指定でメモリを確認しているところ
実装としては、まず引数に - があるかを見て、単独アドレスか範囲指定かを分けています。範囲指定のときは、開始アドレスが終了アドレスより大きければ ? にします。
`PARSE_DUMP_RANGE:
tst ARG2_LEN
beq PARSE_DUMP_FAIL
...
ldx DUMP_ADDR
jsr CMP_X_DUMP_END
bhi PARSE_DUMP_FAIL
clc
rts`
地味に入れておいたのが、$FFFF 付近の扱いです。例えば DFFF0 は 64 バイト表示しようとすると、普通に足し算すると $0000 側へラップしてしまいます。これは見た目にも危ないので、$FFFF までで止めるようにしました。
SWI を使うソフトウェアブレークを入れた
次にブレークポイントです。外部回路を使ったハードウェアブレークではなく、今回はソフトウェアブレークにしました。指定アドレスの 1 バイトを保存して、そこに SWI 命令、つまり $3F を書き込みます。
`] B0105
] G0100
BRK 0105 A=42 B=00 X=0105 SP=1F20 CC=C0
]`
RAM 上のプログラムに SWI ブレークを設定して停止したところ
ブレークポイント v1 は単一箇所のみです。複数ブレークポイントは欲しくなりそうですが、まずは単一で十分です。ROM容量も RAM ワークも無限ではないですし、実機確認用としては、1箇所止められるだけでもかなり便利です。
ROM 領域への B はエラーにしています。この方式は対象アドレスへ $3F を書くので、ROM には置けません。SBC6800 では RAM 上にロードした小さいプログラムを追う用途に絞っています。
止まったときにレジスタを表示する
`BRK 0105 A=12 B=34 X=2000 SP=1F20 CC=C0`
見たいのは、まず「どこで止まったか」です。次に、A、B、X、SP、CC がどうなっているかです。
ここで少し気をつけたのが、PC の扱いです。MC6800 の SWI では、スタック上に積まれる復帰 PC は SWI 命令の次を指します。でも、ブレークポイント表示として見たいのは SWI の次ではなく、ブレークを置いたアドレスです。
` ldaa BRK_SAVE_PC
staa BRK_ADDR
ldaa BRK_SAVE_PC+1
staa BRK_ADDR+1
ldx BRK_ADDR
dex
stx BRK_ADDR`
さらに R で再開するときも、元命令から実行し直したいです。そのため、ブレークヒット時に元バイトを復元し、保存しておいた SWI フレームの PC もブレークアドレスへ戻します。
ここはけっこう大事です。もし PC が SWI の次のままだと、元命令を飛ばしてしまいます。ブレークポイントで止まったのに、再開したら止めた命令を実行しない、ということになります。
簡易逆アセンブル U コマンド
次は U コマンドです。これは本格的な逆アセンブラではありません。指定アドレスから 8 命令だけ、主要命令を表示します。未対応 opcode は DB $xx として 1 バイト進めます。
`] U0100
0100 86 LDAA #$12
0102 B7 STAA $0120
0105 3F SWI
0106 FF DB $FF
]`
U コマンドで HELLO 付近を簡易逆アセンブルしているところ
対応したのは、今の monitor やデータパック確認でよく見る命令を中心にしています。未対応命令を全部エラーにするのではなく、DB $xx で表示して進めるようにしたのは、実機確認ではその方が便利だからです。
簡易アセンブラは今回は入れていません。ROM の中でアセンブラまで持つより、短いパッチは M、まとまったコードは PC 側でアセンブルして L で流す方が、今の段階では現実的だと思います。
コマンドリファレンスも作った
今回の最後に、コマンドリファレンスも作りました。今までも要件書にはコマンド一覧がありました。ただ、要件書は「何を作るか」を書く場所なので、実際に端末からどう打つか、何が表示されるか、どこに制限があるか、という使い方の説明としては少し弱かったです。
そこで docs/usage/monitor_commands.md を追加しました。
追加した ROMモニタ コマンドリファレンス
この文書には、今回追加したコマンドだけでなく、既存の M、G、L も含めて、端末操作としての使い方を書いています。
今回のまとめ
Dコマンドの 64 バイト表示と範囲指定- SWI を使う単一ソフトウェアブレークポイント
- ブレーク停止時のレジスタ表示
Rによる元命令からの再開Uコマンドによる簡易逆アセンブル- 利用者向けコマンドリファレンス
これで、「ロードして動かす」から「止めて見る」方向へ少し進みました。本格的なデバッガではありませんが、8bit 機の ROMモニタとしてはだいぶ触りやすくなったと思います。
今回の対応はgithubのこちら。
特に B、R、U が入ると、RAM 上に小さいコードを置いて試すときの見通しがかなり変わります。次はこの機能を使って、BASIC やデータパック側の挙動確認をもう少し進めていけそうです。