画面に直接マシン語を書く?

Tweet

 2019年3月30~31日にテレビ朝日系で放送されたドラマ「名探偵 明智小五郎」。発生する事件の設定をサイバー犯罪とし、西島秀俊演じる明智小五郎をスーパーハッカー、伊藤淳史演じる小林芳雄を少年ではなく警視庁の刑事にという、大胆に換骨奪胎した設定だったわけですが、そのかなり荒唐無稽な筋書きはエンターテインメントなので細かく突っ込んだりしないことにしまして、最後に明智が自らの原点としてMZ-80K2Eを取り出してきたというシーンがちょっとだけ話題になりました。

 骨董品に火を入れて「懐かしいなぁ」とポチポチキーを押しながら呟いていたシーンは印象的だったのですけど、いろいろと知ってると気になることがあるのですよ…何もロードしないでキー叩いてますよね? モニタSP-1002のコマンドは貧弱で、テープからシステムを読み込んでブートする以外はほとんど機能もなく…いや指定番地にジャンプするぐらいはできますけど、電源投入直後では何か改造してROMでも搭載してない限り有用な飛び先もありませんしね? 明智さん、あなたはいったいモニタコマンド画面で何してたんですか?

…いやいや。

 ドラマの明智探偵ほどのスーパーハッカーなら、絶対知らないはずはありません。黎明期のパソコンはハード的にシンプルなので、メインメモリもVRAMもCPUから見る限りにおいて機能的な差はありません。これが何を意味するかと言うと、VRAMに書かれた文字列をプログラムと見做して実行することが可能だということです。明智探偵はきっと、即席でマシン語モニタをキーボードから文字列として入力し、さらにそれを使ってもっと大きくて複雑なプログラムを16進バイト値で入力していったに違いありません。

 でなければ、FDDもなく、ただの1本のテープも使わず、あんなに楽しそうな顔をして操作するなんてあり得ないじゃないですか…。


いきなり脱線しました

 まぁドラマの件はね、ネタなんですけれど。電源入れただけでとりあえずポチポチ打って不自然じゃないということではPC-8001の方が良かったんじゃないかと思いつつ、手つかずの箱を取り出したら懐かしいのが入ってたというくだりが、本体とモニタを接続するシーンまで入れちゃうとくどくなっちゃうしな…と同情もしつつ。まぁ演出次第ですけどね。

 PC-8001など、モニタコマンドのプロンプトが'*'(アスタリスク)で、MZ-80K/CもSP-1002のプロンプトが同じマークだから、MZ用のマシン語ゲームを入力しようと思ってせっかくBASICがロードされてるのに電源断→入してリセットしちゃう人がいましてね、店頭で。そう、昔のマイコン少年がマイコンショップや家電量販店のマイコンコーナーに入り浸ってたりする時代の話ですよ。

 やおら雑誌を開いて、猛然とダンプリストを入力し始めるのです。でも悲しいかな、彼にはBASICの知識しかなかったようでした。マシン語を入力するには、入力ツールというものが必要なことを知らなかったのです。ダンプリストの先頭にあるアドレスを行番号みたいなものと思って(当たらずとも遠からずだけど)律儀に入力し、そう言えば末尾のチェックサムまで打ち込んでいましたかね。SP-1002のコマンドプロンプトで。

 ある程度入力したところで、どれだけ動くのか確かめるべく彼は「RUN」と入力しました。もちろん、MZは何の反応も示しません。不審に思った彼は「LIST」とも入力しました…がそんなコマンドはありません。これまでの努力が無駄だったことに気づいた彼は、悄然とした様子で本を閉じ、MZの前から立ち去りました…その後彼はMZを二度と触らなかったりしたのでしょうか…あなたの経験がちょっと足らなかっただけのことなのです、せめてMZのことは嫌いにならないで…というかそもそも入力していたリストはMZ用のものだったのかな…。

 なおMZを触りたくて順番待ちしていた私にとっては、せっかくロードされていたBASICが消されてしまったので待った甲斐もなくなってしまいました。余計なことをしてくれたものです…!

 …と、普通なら純正のマシンランゲージなり、雑誌によく掲載されたマシン語入力ツールをあらかじめロードしておかないと、MZではマシン語の入力は不可能だと相場が決まっていました。BASICをロードしてもそういう機能が追加されるわけではありません。MZ-80BになってようやくBASICのモニタに1バイト単位で入力できるコマンドが装備されたのでしたね。あまり使いやすくなかったのと結局チェックサム確認ツールは必要になったので、統合的に使えるマシン語入力ツールの投稿・掲載は続いたのですけど。

 しかし。MZ-80K/Cシリーズには、先に紹介したように画面上の文字をプログラムに見立てて実行させる裏技が存在しました。いろいろ制約はあるんですけど、マシン語入力ツールを使わなくてもマシン語プログラムを直接入力する手段がないわけではない、ということなんです。

なんでもコピープログラム

 月刊アスキー・1980年11月号の「Direct Mail Area」、つまり読者投稿欄にて「これは便利! MZ-80なんでもコピープログラム」という題でお便りが紹介されました。BASICなどをロードした直後にリセットするなどしてSP-1002に戻り、画面の左上隅からある呪文のような文字列を打ち込んで$D000から実行させれば、直前にロードしたプログラムのコピーがSAVEできるというものです。

 その呪文のような文字列というのがこちらです。なお当該号を入手して確かめてみたのですが、印刷の問題か読み取れない文字もあったので、プログラムの内容から推測して補完しています。

 画面の左上隅、つまり$D000からのダンプリストはこんなふうになります。

     +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF
D000 3E 67 87 D6 01 32 0B D0-32 0E D0 00 21 00 00 24 >g...2..2...!..$
D010 00 C3 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
D020 00 00 00 00 00 00 00 00-6B 07 0F 14 0F 64 04 20 ........k....d.
D030 20 20 00 00 00 00 00 00-00 00 00 00 00 00 00 00   .............. 

 逆アセンブルしてみるとこう。

D000  3E67        LD A, 67H
D002  87          ADD A, A
D003  D601        SUB 01H
D005  320BD0      LD (0D00BH), A
D008  320ED0      LD (0D00EH), A
D00B  00          DB 00H
D00C  2100        DW 0021H
D00E  00          DB 00H
D00F  2400        DW 0024H
D011  C30000      JP 0000H

 ミソはモニタサブルーチンの$0021(WRINF)と$0024(WRDAT)を立て続けに呼び出しているところです。LOADの際にファイル名や配置アドレスといった情報がワークに書き込まれるのですが、SAVEでも同じワークエリアを使用していることから、LOADの直後にSAVEを行うとLOADした時の情報を使ってSAVEする…つまりコピーができるというわけです。BASICの場合は起動直後に「USR(33):USR(36)」を実行すれば同じ事が出来ますが、インタープリタPASCALみたいにダイレクトモードがないものなどではムリですからね。

 ただ逆アセンブルリストを見てみると0021と0024という数値はあってもCALL命令はなく、またそこに到達するまでに何かごちゃごちゃと余計な処理…CALL命令のコード0xcdを計算で求めて、CALL命令があるべき場所にそれを書き込んでいるなどしています。なんでこんなに回りくどいことをしているのか?

 それは都合良く0xcdという値のキャラクタをキーボードから入力できないからです。VRAMに書かれた文字はASCIIコードではなくディスプレイコードになるので、ちょっとディスプレイコードの一覧を見てもらいましょう。

 0xcdは右から4列目・下から3段目なので、逆向きの人形マークになります。というか右から4列目は画面制御文字以外全部キーボードから入力できない文字ばかりですね。参考として、BASICマニュアルにあるキーボードの配列を載せておきましょう。

「GOTO$D000」を実行するまでに画面に置いておける文字は全てキーボードから入力できるものでなければならず、それ以外のコードがどうしても必要なら実行時にセルフパッチするしかないというわけです。

 ただ逆アセンブルリストを見てみるともうちょっと改良できそうな気がしますよね。SUB命令で1引くんじゃなくてDEC命令なら1バイトで済むだろうというのがまず思いつくところですが、同じようにアスキーの編集部でもいろいろ検討してみたところ、ここまでシンプルになりました。

 見覚えのある方もたくさんいらっしゃるんじゃないでしょうか。画面に文字を打ち込んだらそれがマシン語、という文脈で引用される時に必ず登場する呪文ですね。先ほどと同様に、ダンプリストと逆アセンブルリストも見てみましょう。

     +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF
D000  AF D4 21 00 AF D4 24 00-C3 00 00 00 00 00 00 00  ..!...$.........
D010  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
D020  00 00 00 00 00 00 00 00-6B 07 0F 14 0F 64 04 20  ........k....d.
D030  20 20 00 00 00 00 00 00-00 00 00 00 00 00 00 00    ..............
D000  AF          XOR A
D001  D42100      CALL NC, 0021H
D004  AF          XOR A
D005  D42400      CALL NC, 0024H
D008  C30000      JP 0000H

 今度はセルフパッチがなく、必要最小限の付加コードのみがあるだけで普通にCALL命令が使われていますね。ん? 普通に?

 使われているCALL命令は条件付きコールですね。しかし実行前にXOR Aがあります。ここではAレジスタの内容をゼロクリアするのが目的ではなく、XOR命令を実行するとキャリーフラグが下りることを利用しています。このため、条件付きコールでも必ず条件が満たされることから無条件でコールが実行されるわけです。なかなかうまい方法だと思います。

キーから入力できないコード

 上記の例では0xcdが入力できないのでなんとかしてそれを補う工夫がありましたが、全体的に見てみれば入力できない文字の方が少なかったりします。一覧にしてみましょう。

コード ニーモニック
40 LD B,B
80 ADD A,B
C0 RET NZ
C7 RST 00H
C8 RET Z
C9 RET
CA JP Z,nn
CB (ビット操作命令)
CC CALL Z,nn
CD CALL nn
CE ADC A,n
CF RST 08H
DF RST 18H
コード ニーモニック
E0 RET PO
E1 POP HL
E2 JP PO,nn
E3 EX (SP),HL
E4 CALL PO,nn
E5 PUSH HL
E6 AND n
E7 RST 20H
E8 RET PE
E9 JP (HL)
EA JP PE,nn
EB EX DE,HL
EC CALL PE,nn
ED (特殊命令)
EE XOR n
EF RST 28H
コード ニーモニック
F0 RET P
F1 POP AF
F2 JP P,nn
F3 DI
F4 CALL P,nn
F5 PUSH AF
F6 OR n
F7 RST 30H
F8 RET M
F9 LD SP,HL
FA JP M,nn
FB EI
FC CALL M,nn
FD (IY系命令)
FE CP nn
FF RST 38H

 1/4に満たないとはいえ、ないとつらい命令がちょこちょこありますね…EDとかCBとか便利な命令がたくさんあるプリフィックスも含まれますし、スタック操作命令ではAFとHLのPUSH/POPがないのも痛いです。そして命令としてだけではなく、定数値としても入力できないということですから、その点にも注意が必要です。

 作れるプログラムはかなり制限されたものになってしまうでしょうが、ここは割り切りが肝要です。UIのために必須の画面をプログラム領域にしてしまっては、そちらの意味でも使い途が制限されてしまいます。領域の大きさも1KBを下回ります。あまり欲張らず、やりたいことの的を絞って考えるのが良いと思います。

超簡易マシン語入力ツール

 というわけで、明智探偵も使ったであろう(単なる妄想)つなぎ役としての「超簡易マシン語入力ツール」を作ってみました。どこかの本または雑誌に似たようなものが掲載されたことがあったらしいのですが未確認ですし、確認したとしてもそのままここに載せるのはアレですので…。

 3行に満たない…いや2行と少し、92バイトを入力して先頭(左上隅なら$D000)から実行すれば動き出します。

 わかりにくい文字について解説しておきましょう。ZN1と0LSに挟まれたピリオドみたいな文字は、英記号のピリオドではなく、カナの「点」です。カナモードで小さい「ェ」の左隣にあるキーです。
 PDとLの間のアンダーラインはこれも英記号ではなくグラフィック文字です。下から3番目の高さにある横線文字で、半濁点のあるキーを英数モードで押します。以後のアンダーラインは皆同じです。
 BXとSの間のマイナスはグラフィック文字です。下から5番目の高さにある横線文字で、カナモードで「ヲ」のキーを押します。
 SSSSの左のスラッシュは英記号の割り算記号ですが、バックスラッシュはグラフィック文字です。小さい「ョ」のシフト位置にあります。

 ちなみに輸出仕様だとこうなります。

 一部違って見えますが、カナなどだった場所に違うパターンのキャラクタが割り当てられているだけで、プログラムとしては全く同じコードです。そしてキーボードのキー配列(というかキーマトリクス)も実は同じなので、全部入力可能なのも同じです。

 起動後、プロンプトのような気の利いたものはありませんがCRを押して改行しても'*'は出てきませんのでこのプログラムが動作中であるとわかるかと思います。

 プログラムの使い方について。先頭に':'(コロン)、続いて4桁のアドレス、さらに2桁ずつのデータをスペースなどで区切らず連続して入力しCRを押すと指定したアドレスから連続してデータが書き込まれます(メモリエディット)。上記の例だと2080番地からAB, CD, EF, 01, 23と書き込まれることになります。

 メモリエディットのデータに'"'(ダブルクォーテーション)を指定すると、次の文字からそのASCIIコードがメモリに書き込まれます。もう一度'"'を入力するとバイト指定モードに戻ります。上記の例だと2090番地から01, 54(T) ,45(E), 53(S), 54(T), 0Dと書き込まれます。これではダブルクォーテーションが入力できませんが、代わりに0x22を書けばいいだけです。

 先頭に'!'(エクスクラメーション)、続いて4桁のアドレスを指定してCRを押すとそのアドレスにジャンプします。コールではなくジャンプであることに注意です(GOTO$もジャンプです)。ちなみに0082番地はSP-1002のホットスタートアドレスです。

 文字→数値変換などモニタサブルーチンを利用した時にエラーになった時(桁数が足りない・変換できない文字が含まれる)はそこで処理を止めてコマンド待ちに戻ったりはしますが、特にエラー処理やメッセージ出力などはしていません。つまり入力が全て正しく行われたのかどうかが確認できないということでもありますが、これは入力者が全て正しく入力してくれていると全面的に期待しているということです。

 さて、マシン語の入力を何行も続けているとそのうち最下行に到達してしまいます。そこでさらに次の行に移れば、画面はスクロールし、せっかく打ち込んだプログラムが消えてしまうことになるでしょう。ですが、このプログラムはリロケータブルに作ってあるのであらかじめ他のアドレスに転送してしまえば問題ありません。プログラム自体に転送機能はありませんが、代わりにこうすればいいんです。

 VRAMの一部をちょっと借りて、LDIR命令で転送してそこにジャンプするというプログラムを入力して実行しています。DEレジスタへのロード、つまり11の次の2バイトが転送先(もちろんリトルエンディアンなのでC800番地は00C8と並ぶ)なのと、最後のジャンプ命令であるC3の次の2バイトに転送先のアドレスをセットするのに注意してください。もちろん元がD000番地からの配置になってなければ21の次の2バイト(転送元アドレス)も調整してくださいね。

 こうやって転送すれば、あとは画面をクリアしても大丈夫です。どれだけスクロールしてもOKです。

 SAVEもできますよ! 手順はこうです。

 まず10F0番地から01(アトリビュート), ファイル名, 0Dと入力します。ダブルクォーテーションでくくればファイル名も楽に入力できます(これがやりたかったのでこの機能をつけた)。
 次に1102番地からサイズ、先頭番地、実行番地の順にリトルエンディアンで並べます。ここでは先ほど転送したプログラムを指定しています。
 そしてまたVRAMの一部を使って0021番地・0024番地をコールしまたプログラムの先頭にジャンプするプログラムを書き、その先頭から実行します。

 「書ける」「ジャンプできる」だけでけっこういろんなことができますね~。ということでここにソースも載せてしまいましょう。さすがに開発はアセンブラ(紅茶羊羹さんのZ80AS)を使いました。

;
; Micro Memory Editor
;
; Oh!Ishi/Nibbles Lab.
;
;          2024.07.27
;

GETL    EQU       0003H         ; Input 1 line
NL      EQU       0009H         ; New Line
GOTOX   EQU       016AH         ; JP (HL)
HLHEX   EQU       0410H         ; HL to ASC
HEX2    EQU       041FH         ; A to ASC
BUFER   EQU       11A3H         ; Key buffer

        ORG       0D000H        ; VRAM top address

START:  OR        A
        CALL      NC,NL         ; Set top of line
        LD        DE,BUFER
        OR        A
        CALL      NC,GETL       ; Command input
        LD        A,(DE)
        LD        C,'!'         ; Jump command
        CP        C
        JR        NZ,CHKM

        ; Jump command
        ;
        INC       DE
        OR        A
        CALL      NC,HLHEX      ; Jump address
        JR        C,NXT         ; Bad number
        JP        GOTOX         ; Jump!

RPT2:   JR        STR1          ; Relay

CHKM:   LD        C,':'         ; Memory edit command
        CP        C
        JR        Z,CMDM
NXT:    JR        NEXT

        ; Memory edit command
        ; (bytes mode)
        ;
CMDM:   INC       DE
        OR        A
        CALL      NC,HLHEX      ; Edit address
        JR        C,NEXT
        INC       DE            ; Skip address
        INC       DE
        INC       DE
        INC       DE
CMDM1:  LD        A,(DE)
        LD        C,0DH         ; End of input
        CP        C
        JR        Z,NEXT
        LD        C,22H         ;'"'
        CP        C
        JR        Z,STR         ; to string mode
        OR        A
        CALL      NC,HEX2       ; Get data
        JR        C,NEXT
        LD        (HL),A
        INC       HL
        JR        RPT1

        ; Memory edit command
        ; (string mode)
        ;
STR:    INC       DE
STR1:   LD        A,(DE)
        LD        C,0DH         ; End of input
        CP        C
        JR        Z,NEXT
        LD        C,22H
        CP        C
        JR        NZ,STR2
        INC       DE
        JR        CMDM1         ; to bytes mode
STR2:   LD        (HL),A
        INC       HL
        INC       DE
        JR        RPT2

NEXT:   JR        START
RPT1:   JR        CMDM1         ; Relay

        END       0D000H

 一見無意味に前へ後ろへジャンプしている箇所がありますが、これは今回の条件でも相対ジャンプが可能なような工夫です。-33バイト以内のジャンプが入力できないので、一旦大きな(-33バイト以遠の)ジャンプをして戻ってきたり、敢えて遠くにジャンプして-33バイトを超える距離になるよう調整しているというわけです。

 この程度の調整で解決できて助かりました。セルフパッチをしないといけなくなると、今配置されているアドレスを取得する必要が生まれ、かなり面倒なことになったでしょうから…。

 ツールも一時はダンプやセーブのコマンドを備えたり、メモリ書き込みコマンドも現在の値を表示したりとかなり親切な仕様で作りかけていたのですが、途中で「常用するツールじゃないんだから短くすべきだ、長くとも256バイト未満、もちろん短ければ短いほどいい、そのためになら不親切極まりなくてもいいじゃないか」と考えを改めまして、今の仕様になった次第です。

またひとつ、不可能がなくなった…

 いろいろと、そして明智探偵の件についても先ほど書いたように、いわゆるネタ話なんですけどね…ネタでもプログラム自体はちゃんと動いて、なんだか実用くさい顔をしているというのが面白いのであって。

 ひとつの想定としては本体のみのMZ-80Kがヤフオクなんかで出品されていたのをゲットして、幸いにして稼働品ではあったもののシステムのテープなどが一切ないので、ようやく手に入れた雑誌に載っていたゲームやツールを入力したいと思った時、まずはこのページに載せたプログラムを入力すれば、ひとまず使い始めることができる…というものです。かつてミニコンではブートに必要なIPLのコードがROMとして内蔵されておらず、使用者が前面のパネルのスイッチをパチパチいじって入力する必要が毎回あったのですが、長さも256バイト未満なので何回も操作するうちすっかり覚えてしまった…なんてこともよくあったと聞きます。それに比べれば92バイト程度なら覚えるのもかなり簡単だろうと思えてきますよね。私は覚えてませんが。

 今はエミュレータやサポートツールが充実していますので、わざわざ不親切なUIのプログラムを使わなくてもそういったソフトを利用すれば事足りるようになりました。

 そんなわけでこのページは、キー入力できる範囲で直接マシンコードを画面に打ち込んでプログラムを作るとしたらどれぐらいのものが作れるのか、どういう制約があるのかを調べてみた…というのを裏テーマとして制作した、と思えばきっと意味があるんだろう…ということにしておきます。

 探せば似たようなことをされてる方もいらっしゃるみたいですし、今回のプログラムよりもさらに割り切って短くすることを考えるのも面白いと思います。パソコンがずっとずっとシンプルだった頃のトンチを楽しみましょう。

所蔵品一覧に戻る