スタック操作

スタック(stack)とは Last In, First Out というデータ構造で、積み重ねるという意味を持ち、最後に載せたものを最初に取り出します。 スタックはレジスタをメモリに退避する場合によく使われます。一番上に載っているデータのメモリアドレスを示すためにスタックポインタ(RSP)を使います。 スタックポインタはレジスタからメモリにデータを書き出し(PUSH)たり、メモリからレジスタに読み出す操作(POP)を行うと自動的に更新されるため、メモリアドレスを意識する必要はありません。スタックを読み書きするデータの単位は64ビットです。16ビットを読み書きすることもできますが、スタックポインタの値が8の倍数でなくなると遅くなるため使うべきではありません。 また、スタック操作命令のデフォルトのオペランドサイズは 64 ビットになっていて、64ビットレジスタを使う場合でも R8-R15 のレジスタを操作対象としなければREXプリフィックスを使う必要はありません。

下の図は、RAX に 0xA7A6A5A4A3A2A1、 RBX に 0xB7B6B5B4B3B2B1、RCX に 0xC7C6C5C4C3C2C1という64ビットの値が入っている場合に、PUSH/POP命令を使った場合のメモリの状態を示しています。例えば、左端は PUSH RAX を実行した直後の RSP 付近のメモリの内容を示しています。PUSH RAX を実行した直後は RSP はRAXの内容が書き込まれたメモリアドレスを示しています。その後、PUSH RBX を実行すると中央、PUSH RCX を実行すると右端の状態になります。RSPの値が 8 ずつ小さくなって、順にメモリ上に積み重なっていく様子がわかります。また、スタックポインタが示しているバイトは最下位のバイトになります。この形式をリトルエンディアンといってインテルが昔から採用している方法です。メモリアドレスの小さいほうに最上位のバイトを格納する形式はビッグエンディアンと呼び、モトローラが採用していました。ARM、PowerPCなどのCPUはどちらの形式も設定できます。メモリダンプした場合にはインテルの形式は逆順に並んでいるように見え、またメモリの読み書きで異なったサイズを使うと上位、下位が反転してしまう(気がする)ので注意が必要です。

PUSH

RSPをプッシュすると、PUSH命令を実行する前のRSPの値がプッシュされる。つまり mov [rsp - 8], rsp; sub rsp, 8; のように働きます。

プリフィックス オペコード 命令 説明
- FF /6 PUSH r/m16 r/m16 を RSP-2 のアドレスのメモリに転送し、RSPを 2 減算する。
- FF /6 PUSH r/m64 r/m64 を RSP-8 のアドレスのメモリに転送し、RSPを 8 減算する。
- 50+rw PUSH r16 r16 を RSP-2 のアドレスのメモリに転送し、RSPを 2 減算する。
- 50+rd PUSH r64 r64 を RSP-8 のアドレスのメモリに転送し、RSPを 8 減算する。
- 6A PUSH imm8 imm8 を64ビットに符号拡張してプッシュする。
- 68 PUSH imm16 imm16 を64ビットに符号拡張してプッシュする。
- 68 PUSH imm32 imm32 を64ビットに符号拡張してプッシュする。
- 0F A0 PUSH FS FS をプッシュ、RSP から 2 を減算する。
(66h でオーバーライドすると16 ビット操作になる)
- 0F A0 PUSH FS FS を64ビットにゼロ拡張してプッシュする。
- 0F A8 PUSH GS GS をプッシュして、RSPから 2 を減算する。
(66h でオーバーライドすると16 ビット操作になる)
- 0F A8 PUSH GS GS を64ビットにゼロ拡張してプッシュする。

POP

RSPの値を増加させてから、スタックの内容が転送される。つまり add rsp, 8; mov rax, [rsp - 8]; のように働く。

プリフィックス オペコード 命令 説明
- 8F /0 POP m16 RSPの示すメモリ内容をm16 に転送し、RSPに 2 を加算する。
- 8F /0 POP m64 RSPの示すメモリ内容をm64 に転送し、RSPに 8 を加算する。
- 58+ rw POP r16 RSPの示すメモリ内容をr16 に転送し、RSPに 2 を加算する。
(REX.R) 58+ rd POP r64 RSPの示すメモリ内容をr64 に転送し、RSP に 8 を加算する。
- 0F A1 POP FS RSPの示すメモリ内容を FS に転送し、RSPに 2 を加算する。
- 0F A1 POP FS RSPの示すメモリ内容を FS に転送し、RSPに 8 を加算する。
- 0F A9 POP GS RSPの示すメモリ内容を GS に転送し、RSPに 2 を加算する。
- 0F A9 POP GS RSPの示すメモリ内容を GS に転送し、RSPに 8 を加算する。

PUSHF

プリフィックス オペコード 命令 説明
- 9C PUSHF EFLAGS の下位16 ビットをプッシュする。
(66h でオーバーライドすると16 ビット操作になる)
- 9C PUSHFQ RFLAGS をプッシュする。

POPF

プリフィックス オペコード 命令 説明
- 9D POPF RSPの示すメモリ内容をEFLAGS の下位16ビットに転送する。
REX.W 9D POPFQ RSPの示すメモリ内容をゼロ拡張してRFLAGS に転送する。

CALL

プリフィックス オペコード 命令 説明
- E8 cd CALL rel32 相対near コール、次の命令とディスプレースメント相対。64 ビットモードでは、64 ビットに符号拡張された32 ビットのディスプレースメント。
- FF /2 CALL r/m64 絶対間接near コール、r/m64 でアドレスを指定。
- FF /3 CALL m16:16 絶対間接far コール、m16:16 でアドレスを指定。32 ビットモードの場合、セレクタの指示先がゲートであれば、RIP はゼロ拡張された32 ビットのディスプレースメントであり、ゲートから取得される。指示先がゲートでなければ、RIP はゼロ拡張された16 ビットのオフセットであり、命令で参照されるfar ポインタから取得される。
- FF /3 CALL m16:32 64 ビットモードの場合、セレクタの指示先がゲートであれば、RIP は64 ビットのディスプレースメントであり、ゲートから取得される。指示先がゲートでなければ、RIP はゼロ拡張された32 ビットのオフセットであり、命令で参照されるfar ポインタから取得される。
REX.W FF /3 CALL m16:64 64 ビットモードの場合、セレクタの指示先がゲートであれば、RIP は64 ビットのディスプレースメントであり、ゲートから取得される。指示先がゲートでなければ、RIP は64 ビットのオフセットであり、命令で参照されるfar ポインタから取得される。

RET

プリフィックス オペコード 命令 説明
- C3 RET call 命令の次の命令に戻る。
- CB RET call 命令の次の命令に戻る(far)。
- C2 iw RET imm16 call 命令の次の命令に戻り、imm16 バイトをスタックからポップする。
- CA iw RET imm16 call 命令の次の命令に戻り、imm16 バイトをスタックからポップする。

IRET

プリフィックス オペコード 命令 説明
- CF IRET 割り込みリターン(オペランドサイズ:16 ビット)
- CF IRETD 割り込みリターン(オペランドサイズ:32 ビット)
REX.W CF IRETQ 割り込みリターン(オペランドサイズ:64 ビット)

INT

プリフィックス オペコード 命令 説明
- CC INT 3 割り込み3 - デバッガへのトラップ。
- CD ib INT imm8 割り込みベクタ番号の即値バイトによる指定。