スタック
> 汎用命令 (1)
スタックは、レジスタの値を一時的に退避させて、後で元に戻したい場合などに使う、読み書き可能なメモリ領域です。
関数の引数を渡したり、関数のローカル変数の領域としても使用できます。
rSP レジスタは、スタックポインタと呼ばれ、現在のスタックのアドレス位置が格納されています。
rSP は、ソフトウェア側が自由に加算/減算したりして、使うことができます。
スタック領域は、メモリ上においては、下方向ではなく、上方向に値が積み重なります。
そのため、スタックに値が格納される場合、rSP はサイズ分減算されます。
PUSH 命令で、オペランドの値をスタックに格納し、POP 命令で、スタックの先頭から値を読み込みます。
スタックは、レジスタの値を一時的に退避させて、後で元に戻したい場合などに使う、読み書き可能なメモリ領域です。
関数の引数を渡したり、関数のローカル変数の領域としても使用できます。
rSP レジスタは、スタックポインタと呼ばれ、現在のスタックのアドレス位置が格納されています。
rSP は、ソフトウェア側が自由に加算/減算したりして、使うことができます。
スタック領域は、メモリ上においては、下方向ではなく、上方向に値が積み重なります。
そのため、スタックに値が格納される場合、rSP はサイズ分減算されます。
PUSH 命令で、オペランドの値をスタックに格納し、POP 命令で、スタックの先頭から値を読み込みます。
スタックに格納されるサイズ
PUSH 命令で、スタックに値が格納される場合、オペランドの実際のサイズと、スタックに格納されるサイズは、異なる場合があるので、注意してください。
64bit モードで、以下のオペランドが指定された場合の、格納サイズの違いは、以下のようになります。
64bit モードの場合、オペランドサイズはデフォルトで 64bit になります。
オペランドにレジスタ/メモリを指定する場合は、16bit か 64bit のサイズでしか指定できません。
(オペランドサイズ・プレフィックス (66h) がある場合は、16bit)
オペランドが即値の場合は、8/16/32bit いずれのサイズでも、エンコーディングはできます。
ただし、8bit/32bit の場合は、64bit に符号拡張されて、64bit 値として格納されます。
つまり、オペランドサイズ・プレフィックス (66h) がある場合は、そのサイズが優先されて 16bit の扱いになりますが、それ以外の場合は、常に 64bit 値として扱われて、スタックに格納されます。
このあたりがややこしいので、スタックに値を格納する場合は、常に 64bit サイズになるように注意し、16bit サイズのオペランドを指定しないようにしてください。
64bit モードで、以下のオペランドが指定された場合の、格納サイズの違いは、以下のようになります。
レジスタ/メモリ 64bit | 64bit の値がそのまま格納されます。 |
---|---|
レジスタ/メモリ 16bit | 16bit の値が、そのまま 16bit サイズで格納されます。 |
8bit/32bit 即値 | 64bit に符号拡張されて、64bit サイズで格納されます。 |
16bit 即値 | 16bit の値が、そのまま 16bit サイズで格納されます。 |
64bit モードの場合、オペランドサイズはデフォルトで 64bit になります。
オペランドにレジスタ/メモリを指定する場合は、16bit か 64bit のサイズでしか指定できません。
(オペランドサイズ・プレフィックス (66h) がある場合は、16bit)
オペランドが即値の場合は、8/16/32bit いずれのサイズでも、エンコーディングはできます。
ただし、8bit/32bit の場合は、64bit に符号拡張されて、64bit 値として格納されます。
つまり、オペランドサイズ・プレフィックス (66h) がある場合は、そのサイズが優先されて 16bit の扱いになりますが、それ以外の場合は、常に 64bit 値として扱われて、スタックに格納されます。
このあたりがややこしいので、スタックに値を格納する場合は、常に 64bit サイズになるように注意し、16bit サイズのオペランドを指定しないようにしてください。
NASM での即値の PUSH
NASM において、PUSH 命令で即値が指定されている場合は、符号付きの数値として扱われ、その値が、適切でかつ一番小さいサイズになるように、エンコードされます。
push 127 ; 6A 7F (8bit) push 255 ; 68 FF 00 00 00 (32bit) push 0xffff ; 68 FF FF 00 00 (32bit) push -1 ; 6A FF (8bit) push -200 ; 68 38 FF FF FF (32bit) push strict word 200 ; 66 68 64 00 (16bit 強制) push strict dword 0xffff_ffff ; 68 FF FF FF FF (32bit 強制) warning: signed dword immediate exceeds bounds [-w+number-overflow] warning: dword data exceeds bounds [-w+number-overflow]
- push 255
これは、符号なし 8bit 値として見た場合、本来は 8bit の即値で良いはずなのですが、8bit 即値は、64bit に符号拡張されてからスタックに格納されるため、8bit 即値の 255 として指定すると、実際にスタックに入る値は、0xffff_ffff_ffff_ffff (64bit) になります。
この場合、符号付きの数値として、255 が表現できるサイズで出力されるため、32bit 即値になります。
16bit 即値にならない理由は、16bit オペランドサイズで指定すると、スタックに格納されるサイズは、2 byte になるためです。
NASM では、(x64 の場合) 常に 64bit サイズの値がスタックに格納されるように、オペランドサイズが調整されます。
- push -1
この場合、結果として、符号付き整数 -1 の値が、64bit の同じ値として格納されれば良いので、符号付き 8bit の -1 として、8bit 即値の 0xFF が指定されます。
8bit 即値が 64bit に符号拡張されて、スタックには 0xffff_ffff_ffff_ffff (-1) の 64bit 値が格納されます。
- push strict word 200
PUSH 時に、即値のサイズを強制したい場合は、STRICT キーワードと、サイズ指定キーワードを使います。
NASM の最適化が有効な場合、サイズ指定キーワードだけでは、任意のサイズに強制できません。
STRICT キーワードは、サイズの最適化を無効にします。
この場合、スタックに格納される値は、2 byte サイズになることに注意してください。
- push strict dword 0xffff_ffff
32bit 即値になるように強制していますが、この場合は、警告メッセージが出ます (出力は通常通り行えます)。
0xffff_ffff は、符号付き 32bit 整数では表現できないので、そのようなメッセージが出ています。
ただし、結果としては、64bit に符号拡張されて、スタックに 0xffff_ffff_ffff_ffff が格納されます。