サブルーチンから戻る際の戻る先のアドレスの設定について。
私の通っている学校の情報科学科では、二年次に「情報科学実験 IA」と称し、 KUE-CHIP2(キューチップ 2)を利用したアセンブリの初歩を学ぶ。 (本来はマイクロプロセッサの基本原理を学ぶのだろうが、実際ほとんどの時間をエミュレータによるアセンブルコーディングに費やした。)
その講義(あるいは実験、実習)の中での設問に対し、同じ学科の知人から質問を受けた。 この文書はその質問への私の解答を多少の修正を加え公開するものである。
まずこれらアセンブリ言語で書かれた命令群は アセンブルを行い機械語等へと変換される。 今やってるのだとアセンブリ命令は 1byte か 2byte の機械語になる。
例:
LD IX, ACC → 68 ( 1byte ) LD ACC, [8D] → 64 8D ( 2byte )
次に、CALL
/ RET
で使用している BA
(Branch All)について。
XX
を任意のアドレスとすると、これは
BA XX
で Program Counter を XX
にするという命令。
PC
が変更されれば実行される場所が変わるので、
メモリにロードされたプログラムの中を自由に移動できるわけだ。
さて、それではどこに移動するか。
まぁ CALL
は問題ない。
サブルーチンが書かれているアドレスに飛べばいいだけだから。
問題は RET
、サブルーチンから戻る際。
もちろん戻る際はそのサブルーチンが呼ばれた場所へ戻る。 ただしホントに呼ばれた場所に戻っちゃうと もっかいサブルーチンを呼び始めるので、 正確には呼ばれたトコのすぐ次の場所へ戻る必要がある。
その呼ばれたトコの次の場所はスタックにつめておく。
さーて、ここからが本題。
BA XX
で移動はできるんだが、
この XX
はあらかじめソースコードに書いておかなくてはならない。
アセンブリには変数ってのが存在しないうえ BA
とかの分岐系の命令で使用するアドレスはレジスタすら指定できず、
具体的な数字を書かにゃならんのだ。
ところがサブルーチンはどこから呼ばれるのかはわからない。 (一般に、複数箇所から同じものを何度も呼び出す。) 具体的に数値を書いておく事なんて不可能だし、かといって呼ばれる箇所に対応した数だけのサブルーチンを書くのはめんどうだ、 っていうかわざわざサブルーチンにする意味がねぇ。
そこでウルトラ C な技をしてみる。
ST
命令でメモリやレジスタの値を書き換える事ができるので、
メモリにロードされたプログラムが自分自身を書き換えるという事をやってのける。
もちろん、ここで「書き換える」のは
BA XX
の XX
の部分、BA
で飛ぶ先のアドレスだ。
ここで重要なのが、CA
というもの。
これは Current Address を意味する(と思う)。
アセンブルすると、そのコードが出力された部分のアドレスに置き換わるわけだ。
つまり、CA
を使用するある命令がアセンブル後にプログラム全体の 30byte 目に出力された場合、
CA
の部分には 30 という数字が書かれている。
また CA+6
なら、そのアドレスに 6 を加算した 36 に展開される。
と、ゆーわけで。 結論としては
ST ACC, (CA+3) BA XX
をアセンブルして
75 ?? 30 XX
という機械語を得る。
ここで CA
が ST
命令のアドレスをさすわけだから、
??
の部分は、 ST
命令のアドレスの 3byte 先の XX
のアドレスに置き換えられている。
つまり、この二行の命令は、XX
を ACC
の値で書き換えるという意味だ。
あとは XX
に入れるべき値、つまり戻る先のアドレスをあらかじめスタックにぶちこんでおく事により、
その直前で ACC
に読み込めば呼ばれた場所(の次)に戻る事ができるのだ。