Worm プログラム(8085, CASLⅡ)

経緯

ある日、古い資料を整理していたら、ドットプリンタで印刷されたアセンブラのソースコードが出てきた。
以下は、その印刷物をスキャンした画像である。
スキャン画像
_images/asm_worm01.jpg _images/asm_worm02.jpg
拡大1 pdfを表示 拡大2 pdfを表示
中身は、1989年にマイコン研修を受講した時に自由課題として作成した8085アセンブラのプログラムだった。

:制作年月日:1989/10/03

昔の研修資料はすべて捨てたはずなのに、これだけは、大切に保管されていた。何のためのプログラムか?
25年前の私は、何をしようとしていたのか解読を試みた。

8085版worm

8085について

8085はIntel社の8bit CPUで、IntelのiCOREやATOMのご先祖様にあたる。
私が実習で使用したマイコンボードは、クロックは4MHz、RAM16Kbyteくらいの仕様だったと思う。
Gameboyで使われていたZ80は、8085の改良版CPUである。

<https://ja.wikipedia.org/wiki/Intel_8085>

8085版wormソースコードの解読

ソースコードを読んでみると、自分自身のコードをコピーしてメモリ内で増殖していくWormプログラムだった。
自分自身のコードを、メモリの下位から上位に向かってコピーし、コピー先の先頭にプログラムカウンタを移動することにより、メモリ内を全て同じコードで埋め尽くしていく。
_images/worm_image.png
ただ、ターゲットである8085は、初期の8bitCPUなので、相対ジャンプのコードを持っていなかった。
そこで、これを克服するために、以下の方法で擬似的に相対ジャンプを実現した。
  1. 自分自身のプログラムカウンタをいったん、スタックに退避する。
  2. スタック内容に移動距離分の加算する。
  3. スタックの内容をプログラムカウンタに戻し、そのアドレスにジャンプする。

8085版wormソースコード

; リモート・モニタ内部のユーティリティルーチン
DERAY           EQU O300H
DISP4H          EQU O2D5H  ; HLに表示したいデータを入れてコール4桁表示

; 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

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

worm.asm ソースコードのダウンロード

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

worm.cas ソースコードのダウンロード

CASLⅡ版wormの実行

今回は、CaslBuilderというCASLⅡシミュレータを利用したので、その利用方法を解説する。

  1. 以下のページの解説を読み、CaslBuilderをダウンロードしてインストールする。

<http://hyamag.la.coocan.jp/caslbuilder.html>

  1. CaslBuilderを起動し、ファイル(F)-開く(O)でworm.casを読み込む。
_images/CaslBuilder_open.png
  1. 実行(R)-ビルド(M)で、実行用コードを生成する。
_images/CaslBuilder_build.png
  1. 実行(R)-デバッグ開始(S)で、デバッグウィンドウを開く。
_images/CaslBuilder_debug.png

5.F5 実行,F6 アニメート実行,F7 トレース,F8 ステップを使って、実際にコードの挙動を観察する。

_images/Debug1.png _images/Debug2.png