アドレス指定

アドレス指定
オペランドのソースや宛先として、任意のメモリ位置のアドレスを指定したい場合は、[ ] で囲んで、その中でアドレス位置を指定します。

アドレス位置は、「base + offset + index * scale」の形式で指定することができます。
これらは、ModRM バイト、SIB バイト、ディスプレイスメントの3つ値によって、エンコードされます。

base と index には、任意の汎用レジスタを指定できます (RIP + offset32 の形式の場合のみ、RIP レジスタが使えます)。
scale は 1, 2, 4, 8 のいずれか。

mov eax, [addr]
mov [addr], eax

mov eax, [rax + rbx*4 + 100]

以前にもやりましたが、NASM はデフォルトで絶対アドレスを出力するため、ラベル位置のメモリを参照する時に、RIP 相対アドレスでアドレス指定するようにしたい場合は、[rel addr] というように、REL キーワードを前に付けるか、「default rel」で、RIP 相対アドレス指定をデフォルトにします。

逆に絶対アドレスを指定したい場合は、ABS キーワードを使って、[abs addr] というように指定できます。
アドレスサイズ
base と index のレジスタのデフォルトサイズは、動作モードが、16bit か、32bit または 64bit かで異なります。

64bit モード時は、デフォルトのアドレスサイズは 64bit です。
アドレスサイズ・プレフィックス (67h) が付けられている場合、32bit のサイズに変更することができます。

なお、64bit モードでは、アドレスは 64bit で扱うことになるため、基本的に 32bit アドレスサイズを使うことはありません。
(32bit サイズでは、64bit アドレス全体を表現できない)

よって、base と index レジスタは、基本的に両方とも、64bit サイズのレジスタで指定する必要があります。
(エンコード上、base と index を、それぞれ別のサイズのレジスタにすることはできない)
オフセットのサイズ
アドレスのオフセット値 (ディスプレイスメント) として指定できる値は、(64bit モードの場合) 符号ありの 8bit, 32bit の値のみです。
よって、最大で、+- 2GB の範囲のオフセットを指定することができます。
エンコーディングのサンプル
default rel
section .text

nop
label1:

; MOV reg32, reg/mem32 | 8B /r

mov eax, [label1]      ; 8B 05 FA FF FF FF
mov eax, [label1+0xff] ; 8B 05 F3 00 00 00

mov rbx, label1        ; 48 BB 01 00 00 00 00 00 00 00
mov eax, [rbx+5]       ; 8B 43 05
mov eax, [rbx+0xaabb]  ; 8B 83 BB AA 00 00

mov eax, [rbx+ecx*5]   ; レジスタのサイズが異なるのでエラー

※適当なオフセットを指定しているので、実際には実行しないでください。

  • mov eax, [label1] (mov eax, [RIP - 6])
    デフォルトで RIP 相対アドレス指定になっているので、[label1] は、RIP 相対アドレスに置き換えられます。
    この次の命令位置から見た時の label1 の位置は -6 となるので、実際には [RIP - 6] となります。

    RIP 相対アドレスの場合、オフセット値は常に 32bit なので、32bit のディスプレイスメント値がエンコードされています。

  • mov eax, [label1+0xff] (mov eax, [RIP + 0xf3])
    この場合、[RIP + (-12 + 0xff)] という形になります。

    この次の命令位置から見た時の label1 の位置は、-12 です。
    それに 0xff の値を加えると、最終的にオフセット値は 0xf3 となります。

  • mov rbx, label1 (mov rbx, 1)
    この場合は、label1 のアドレス値が RBX レジスタに格納されることになります。
    (この形では、RIP 相対アドレスに置き換えることはできません)

    label1 のアドレス値は、.text セクションの先頭を 0 とした場合、1 になります (先頭に NOP 命令があるため)。
    オブジェクトファイルの場合、絶対アドレスが確定していないので、ラベルを直接値として参照した場合、セクション先頭からの相対位置になります。
    よって、64bit の値、1 が格納されます。

    label1 の絶対アドレスを、PIC に対応する形で取得したい場合は、LEA 命令を使って、lea rbx, [label1] とします。

  • mov eax, [rbx+5]
    オフセット値が 5 で、符号あり 8bit に収まる値なので、ディスプレイスメントは 8bit サイズでエンコードされています。

  • mov eax, [rbx+ecx*5]
    base に RBX (64bit)、index に ECX (32bit) が指定されていますが、2つのレジスタは同じサイズでなければならないので、エラーとなります。
NASM による出力
メモリまたは即値のサイズ指定
MOV 命令などで、宛先オペランドにメモリ、ソースオペランドに即値が指定されている場合、明確なサイズ指定がないと、コンパイラはオペランドのサイズを判断することができません。

そのような場合は、以下の、サイズ指定のキーワードを使います。
アドレス指定の前 ([] の外) か、即値の前に記述すると、オペランドのサイズを明示的に指定することができます。

BYTE, WORD, DWORD, QWORD, TWORD, OWORD, YWORD, ZWORD

mov [label], 5 ; エラー

mov dword [label], 5
mov [label], dword 5
アドレス指定形式の最適化
NASM では、通常、アドレス指定のエンコードは、一番小さいサイズになるような形式で出力されます。

例えば [rax * 2] の場合、「base + offset + index * scale」の形式において、「index * scale」のみでの形式は指定できず、base を指定せずに表現しようとすると、結果「index * scale + disp32」の形式でしか指定できないので、32bit ディスプレイスメントの値 0 が必要になります。

しかし、これは [rax + rax] (rax + rax * 1) という形式に置き換えることができるため、この場合、base を RAX とすることで、offset の値が必要なくなります。

通常は、このような最適化は NASM に任せておけば良いのですが、エンコードを確認したい場合などで、最適化を行いたくない時は、NOSPLIT キーワードを使うことができます。

mov ebx, [rax*2]           ; 8B 1C 00 ([rax+rax*1])
mov ebx, [nosplit rax*2+0] ; 8B 1C 45 00 00 00 00

ModRM: 1Ch (mod=0, r/m=4, reg=3[RBX])
SIB  : 00h (base=0[RAX], index=0[RAX], scale=0[x1])
SIB  : 45h (base=5[disp32], index=0[RAX], scale=1[x2])
オフセット値のサイズを強制
ディスプレイスメント (オフセット値) の出力サイズを、サイズ指定キーワードを使うことで、8bit または 32bit に強制することもできます (可能であれば)。

mov eax, [byte rax]    ; [byte rax+0] となる
mov eax, [dword rax+8] ; 通常は 8bit だが、32bit に強制

サイズ指定のキーワードを付けた場合、例えば [byte rax] は、[rax] ではなく、[byte rax+0] となります。
これは、オフセット値が必要ない場合でも、常にオフセット値を付けることが強制されるためです。