Worm プログラム(8085, CASLⅡ)¶
経緯¶
ある日、古い資料を整理していたら、ドットプリンタで印刷されたアセンブラのソースコードが出てきた。
以下は、その印刷物をスキャンした画像である。
中身は、1989年にマイコン研修を受講した時に自由課題として作成した8085アセンブラのプログラムだった。
:制作年月日:1989/10/03
昔の研修資料はすべて捨てたはずなのに、これだけは、大切に保管されていた。何のためのプログラムか?
25年前の私は、何をしようとしていたのか解読を試みた。
8085版worm¶
8085について¶
8085はIntel社の8bit CPUで、IntelのiCOREやATOMのご先祖様にあたる。
私が実習で使用したマイコンボードは、クロックは4MHz、RAM16Kbyteくらいの仕様だったと思う。
Gameboyで使われていたZ80は、8085の改良版CPUである。
8085版wormソースコードの解読¶
ソースコードを読んでみると、自分自身のコードをコピーしてメモリ内で増殖していくWormプログラムだった。
自分自身のコードを、メモリの下位から上位に向かってコピーし、コピー先の先頭にプログラムカウンタを移動することにより、メモリ内を全て同じコードで埋め尽くしていく。
ただ、ターゲットである8085は、初期の8bitCPUなので、相対ジャンプのコードを持っていなかった。
そこで、これを克服するために、以下の方法で擬似的に相対ジャンプを実現した。
自分自身のプログラムカウンタをいったん、スタックに退避する。
スタック内容に移動距離分の加算する。
スタックの内容をプログラムカウンタに戻し、そのアドレスにジャンプする。
8085版wormソースコード¶
; 実行環境は、SBC-85S というマイコンボード
; リモート・モニタ内部のユーティリティルーチン
DERAY EQU O300H ; 時間待ち
DISP4H EQU O2D5H ; HLに表示したいデータを入れてコール4桁表示
; RST 7 の命令を実行するとユーザ・プログラムからモニタ・プログラムに戻る。
; WORM PROGRAM
ORG 8000H ; 初期設定
INIT: LXI H,WORM ; WORMのスタート番地をセット
SHLD COP_ADR ; WORMが走るとプログラムが壊れるので、
JMP WORM ; 相対ジャンプ,コントロール部,ワークエリアを保護するために8100番地からスタート
; 8085は相対ジャンプをサポートしていない
; そこで相対ジャンプに相当するサブルーチンを作った
; DEに相対ジャンプの飛び先との差をセット
; 条件コール命令で呼び出す
R_JMP: CALL WORM_MON ; WORMコントロール部を呼び出す
POP H ; スタックから戻り番地を取り出し
MOV A,L
SUB E ; 後戻りさせたい番地だけひいて
MOV L,A
MOV A,H
SBB D
MOV H,A
PUSH H ; スタックに戻り番地をセットする
RET ; そしてリターン
; WORM コントロール都
WORN_MON:
PUSH PSW
PUSH B
PUSH D
PUSH H
MOV A,L ; HLレジスタの中身を見て
CPI OOH ; 終了かどうかを判定
JNZ NEXT
MOV A,H
CPI 0F0H
JNZ NEXT
POP H ; 終点ならスタックを元に戻して
POP D ; 終了する。
POP B
POP PSW
RST 7 ; SBC-85S では、この命令によりユーザ・プログラム実行後、モニタ・プログラムに戻る。
; また、ユーザ・プログラムの状態(レジスタの値等)を保存してくれる。
NEXT: CALL DISP4H ; WORMの現在位置を表示
CALL DERAY ; 時間待ち
POP H
POP D
POP B
POP PSW
RET
; 使用するメモリは、作業用の2バイトだけ
; 必ずWORMよりも下の番地に確保すること
COP_ADR: DW WORM
; WORM ルーチン
ORG 8100H
WORM: MVI B,END-WORM+3 ; メモリのコピー回数をセット
LOOP: LHLD COP_ADR
MOV A,M ; コピー元から内容を読み出し
LXI D,END-WORM+3 ; WORMの長さを足して
DAD D
MOV M,A ; コピー先に内容を移す
LHLD COP_ADR
INX H
SHLD COP_ADR
LXI D,END-LOOP+3 ; 相対番地の距離をセット
DCR B
END: CNZ R_JMP
8085版wormプログラムの実行¶
解読し、ソースコードを打ち直してみたが、実行できる環境はすでにない。
そこで、ネットで8085エミュレータをいくつか探してテストしてみたが、どれも、「プログラム領域にデータを書き込もうとしている。」というエラーが発生してどれも使えなかった。
通常のプログラミングから考えると、これは、明らかに異常動作と判定されても仕方ない。仕様として認めないのは、当然のことである。
orz
CASLⅡ版worm¶
残念ながら、8085での動作の確認はできなかったので、どのようなプログラムだったかを再現するために、CASLⅡ に移植を試みた。
CASLⅡ は、情報処理技術者試験で使用するアセンブリ言語として開発されたものなので、おそらく、日本で一番有名なアセンブラであろう。
しかし、日常の開発にはまったく使われることはない日本一不遇なアセンブラでもある。
だが、情報処理技術者試験の受験経験者なら、まず、知らない者はいないはずなので知名度が高く、仕様がシンプルなので、今回のような解説に使用するにはちょうどよいと思い採用した。
CASLⅡ版worm¶
以下が、CASLⅡ用に移植したWormのソースコードである。
変数は、すべてレジスタ上に配置してある。実質、20行程度のコードなので、先頭から順番に読んでもらえれば理解できる内容と思われるので、特に解説はしない。
1 ; worm.cas
2 ; CASL2 で作成したコード自己複製プログラム.
3 ; コード自身が, メモリの下位から上位に向かって, 自己複製を繰り返す.
4 ; プログラムブロック
5 ;; 初期化部
6 ;; プログラム開始に必要な初期化を行う.
7 ;; コードを簡素化するために, メモリ参照は使用せず, すべてレジスタだけで実行できるようにした.
8 WORM START
9 0000: 1010 0015 LD GR1,ONE ; GR1:定数1(インクリメントに使用する)
10 0002: 1220 000A LAD GR2,PRGBGN ; GR2:プログラム開始アドレス
11 0004: 1230 000A LAD GR3,PRGBGN ; GR3:プログラム複写元アドレス
12 0006: 1240 0014 LAD GR4,PRGEND ; GR4:プログラム複写先アドレス
13 0008: 1050 0016 LD GR5,OFFSET ; GR5:プログラムをコピーするためのオフセット
14 ;; 自己複製部
15 000A: 1003 0000 PRGBGN LD GR0,0,GR3 ; 自己複製部の1ワード分をGR0にコピーする
16 000C: 1103 000A ST GR0,10,GR3 ; GR0から自己複製部の1ワード分を移動先(+10番地)にコピーする
17 000E: 2431 ADDA GR3,GR1 ; コピーアドレスをカウントアップ
18 000F: 4434 CPA GR3,GR4 ; 自己複製部の終了判定
19 0010: 6102 0000 JMI #0,GR2 ; 条件を満たすまで, PRGBGNへ
20 0012: 1424 LD GR2,GR4 ; プログラム終了アドレスを次の開始アドレスとする.
21 0013: 2445 ADDA GR4,GR5 ; オフセット値を加え, 次のプログラム終了アドレスとする.
22 0014: 0000 PRGEND NOP ; 以下は, コードが自己複製されてくる領域になるので, NOPで埋めておく.
23 ; 定数ブロック
24 0015: 0001 ONE DC 1
25 0016: 000A OFFSET DC 10
26 0017: 0000 DS 1024
CASLⅡ版wormの実行¶
今回は、CaslBuilderというCASLⅡシミュレータを利用したので、その利用方法を解説する。
以下のページの解説を読み、CaslBuilderをダウンロードしてインストールする。
<http://hyamag.la.coocan.jp/caslbuilder.html>
CaslBuilderを起動し、ファイル(F)-開く(O)でworm.casを読み込む。
実行(R)-ビルド(M)で、実行用コードを生成する。
実行(R)-デバッグ開始(S)で、デバッグウィンドウを開く。
5.F5 実行,F6 アニメート実行,F7 トレース,F8 ステップを使って、実際にコードの挙動を観察する。