スタック操作
スタック(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 | 割り込みベクタ番号の即値バイトによる指定。 |