|
ブートローダその8 スタック
|
前回までの内容
これまでで、
- セグメントレジスタはコードセグメント(CS)、データセグメント(DS、ES、FS、GS)、
スタックセグメント(SS)がある
- コードセグメントはCSレジスタと命令ポインタであるEIPレジスタでアクセスできる
- データセグメントはDSとアセンブラ言語のアドレス(オフセット)でアクセスできる
- データセグメントES、FS、GSはアセンブラ言語で明示的に指定してアクセスする
- スタックセグメントはSSとスタックポインタSPでアクセスできる
ということがわかりました。それでは前回の続きになりますが、スタックについて説明したいと思います
スタック
スタックの基本的な概念は英語のstackそのままで”積む”ということになります
プログラムの場合に言うスタックにはデータを積み上げることになります
ではどのように積み上げるのでしょうか
スタックにデータを積み上げる
スタックにデータを積み上げることをプッシュといいます。アセンブラ言語の命令としてPUSH命令でスタックにデータを
積み上げることができます。
PUSH AX
この例で言いますと汎用レジスタAXの値をスタックにプッシュ(積み上げる)できます
このように①PUSH AX ②PUSH BX ③PUSH CXと順番に実行した場合を考えてみます
PUSH命令を実行するたびにスタックにAX→BX→CXの値がそれぞれ積まれていきます
(Tips:順番に積み上げられていることに注意してください)
またPUSH命令が実行される度にスタックポインタSPの値が減っていきます
(上に積み上げるのでSPは減る方向に動きます)
PUSH命令を実行するとハードウェアが自動的にSPの値を減算してくれますので、
特にソフトウェアは何もしなくてもよいです(動作だけ理解できていればよいです)
スタックポインタの指すアドレスが0x0002ずつ減っていくのはなぜでしょうか
何度もでてきましたが現在このCPUが16ビットで動作しているためです
16ビットのCPUでは基本データサイズが16ビットなのでスタックの1つのデータも16ビット(2バイト)となります
ですので、スタック上にPUSHする場合は16ビットサイズが基本となります
もちろん32ビットCPU、64ビットCPUであればそれぞれ32ビット、64ビット単位でのデータ積み上げになります
(1バイトだけスタックに積みたいと思われるときもあるかもしれませんが、ハードウェアが自動的にしているのでできません)
スタックからデータを下ろす
スタックにはデータを積み上げることもできましたがデータを下ろすこともできます
POP AX
この命令はポップ命令といい、スタックに積み上げられたデータを汎用レジスタAXに格納しています
このように①POP CX ②POP BX ③POP AXと順番に実行したときを考えてみます
POP命令を実行するとスタックに積み上げられたデータを下ろして汎用レジスタにデータを移します
(実際にメモリ上のデータが消えるわけではありません)
この際にスタックポインタSPの値が0x0002ずつ増えていきます。
(データを下ろしているのでSPは下の方向にいきます)
このようにしてスタック上にPUSHしたデータはPOPしてデータをとることができます
その他のスタックの使い方
他にもCALL命令という命令があります
CALL Operate_CX
XOR CX, CX
CALL命令はC言語で言うところの関数呼び出しにあたります
CALL命令を実行するとこの例では”Operate_CX”といるうラベル付けされた関数にジャンプします
その際にハードウェアが自動的に戻りアドレスをスタックに積み上げます
この例では次の命令であるXOR命令が置いてあるアドレスをスタックに積み上げて置きます
Operate_CX:
ADD CX, 0x0001
RET
そして”Operate_CX”で関数を実行し終わった後のにRET命令を実行します
RET命令は関数の呼び出し元に戻るアドレスです。どこに戻ればいいのかが記録されているのが
CALL命令で積み上げられていたデータにあります
ですので、RET命令はスタックに積み上げられた戻りアドレスをPOPし、元の場所にジャンプして戻ることができます
その応用としてCALL命令のときに引数として渡したいデータがあるときや、関数でレジスタの値を変更したい
が戻ってきたときには元の値に戻しておきたい場合にPUSHとPOPすることもできます
PUSH CX ;関数から戻ってきてから元に戻したい
PUSH AX ;関数の引数その1
CALL Operate_CX
POP CX ;関数の中でCXが変更されたのでポップしてCXの値を元にもどす
HLT
Operate_CX:
POP CX ;スタックに積まれたAXの値をCXに入れる
ADD CX, 0x0001
MOV BX, CX ;足し算してBXに格納
RET ;元にもどる
また、関数のなかでレジスタを変更するので予め使うレジスタの値をスタックへPUSHしておき
関数から戻る直前でPOPしておけば変更する前の値に戻すことができます
CALL Operate_CX
HLT
Operate_CX:
PUSH CX
ADD CX, 0x0001
MOV BX, CX ;足し算してBXに格納
POP CX ;関数の中でCXが変更されたのでポップしてCXの値を元にもどす
RET ;元にもどる
これでスタックの基本的な使い方ができるようになったかと思います
では次回ブートローダの開発を進めていきましょう