スタック

スタック
> 汎用命令 (1)

スタックは、レジスタの値を一時的に退避させて、後で元に戻したい場合などに使う、読み書き可能なメモリ領域です。
関数の引数を渡したり、関数のローカル変数の領域としても使用できます。

rSP レジスタは、スタックポインタと呼ばれ、現在のスタックのアドレス位置が格納されています。
rSP は、ソフトウェア側が自由に加算/減算したりして、使うことができます。

スタック領域は、メモリ上においては、下方向ではなく、上方向に値が積み重なります。
そのため、スタックに値が格納される場合、rSP はサイズ分減算されます。

PUSH 命令で、オペランドの値をスタックに格納し、POP 命令で、スタックの先頭から値を読み込みます。
スタックに格納されるサイズ
PUSH 命令で、スタックに値が格納される場合、オペランドの実際のサイズと、スタックに格納されるサイズは、異なる場合があるので、注意してください。

64bit モードで、以下のオペランドが指定された場合の、格納サイズの違いは、以下のようになります。

レジスタ/メモリ 64bit64bit の値がそのまま格納されます。
レジスタ/メモリ 16bit16bit の値が、そのまま 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 が格納されます。