x86のアドレッシングモードと使えないレジスタについて

x86CISC命令セットであり、RISC命令セットと異なりメモリ上のデータを命令のオペランドに取ることができます。 x86レジスタは(特に32ビットと64ビットは)おおよそ汎用ですが、一部のレジスタはアドレッシングに使えない制限があります。 よくわからなくなるのでまとめてみました。

16ビット

レジスタは全く汎用ではありません。基本的にはAXCXDXを算術演算に使い、BXSIDIをアドレス計算に使う、ということが想定されているようです。 基本的には、以下のアドレッシングモードが使えます。

  • [addr + offset]
    • addrは、以下の八種類
      • BX
      • BP
      • SI
      • DI
      • BX+SI
      • BX+DI
      • BP+SI
      • BP+DI
    • offsetは、以下の三種類
      • (何も足さない)
      • imm8
      • imm16
  • [imm16]

ただし、このうち[BP](つまり、addrBPで、offsetが「何も足さない」)は、そのエンコーディングが絶対16ビットアドレス指定に奪われているので、使えません。 もちろん、[BP+0]とすることで、1バイト増えてしまいますがエンコーディングすることは可能です。 なお、BPをフレームポインタとして使っている場合、[BP]に入っているのはリターンアドレスのはずなので、普通は使わないと思います。

32ビット

レジスタはほぼ汎用になりました。 基本的には、以下のアドレッシングモードが使えます。

  • [addr + offset]
    • addrは、以下の七種類
      • EAX
      • EBX
      • ECX
      • EDX
      • ESI
      • EDI
      • EBP
    • offsetは、以下の三種類
      • (何も足さない)
      • imm8
      • imm32
  • [imm32]
  • [addr + index * scale + offset](SIBバイト使用)
    • addrは、以下の八種類
      • EAX
      • EBX
      • ECX
      • EDX
      • ESI
      • EDI
      • ESP
      • EBP
    • indexは、以下の八種類
      • EAX
      • EBX
      • ECX
      • EDX
      • ESI
      • EDI
      • (零)
      • EBP
    • scaleは、以下の四種類
      • 1
      • 2
      • 4
      • 8
    • offsetは、以下の三種類
      • (何も足さない)
      • imm8
      • imm32
  • [imm32 + index * scale](SIBバイト使用)

16ビットの時からの伝統で、[EBP](つまり、addrEBPで、offsetが「何も足さない」)は、そのエンコーディングが絶対32ビットアドレス指定に奪われているので、使えません。 16ビットの時同様、[EBP+0]とすることで、1バイト増えてしまいますがエンコーディングすることが可能です。

SIBバイト利用アドレッシングでは、[EBP + index * scale](つまり、addrEBPで、offsetが「何も足さない」)は、そのエンコーディングが絶対32ビットベースアドレス指定に奪われているので、使えません。 同様に、[EBP + index * scale + 0]とすることで、1バイト増えてしまいますがエンコーディングすることが可能です。 indexEBPではなくscale1の場合は、逆順にすればバイト数増加はありません。

八つの汎用レジスタの一員であるにもかかわらず、[ESP + offset]は三種類とも、そのエンコーディングがSIBバイト利用アドレッシングに奪われているため、使えません。 これをやりたい場合は、SIBバイト利用アドレッシングの中にある[ESP + (零)* scale + offset]を使うことで、1バイト増えてしまいますがエンコーディングできます。 scaleは、1を設定するのが一般的です。

ESPは、そのエンコーディングが零レジスタ指定に奪われているので、indexとして指定することができません。 とはいえ、ESPを2倍した値というのが意味を持つ局面はないはずなので、通常は問題となりません。 なお、零レジスタを2倍・4倍・8倍する、というのも意味がないものであり、エンコーディングに無駄があります。

64ビット

汎用レジスタは16本に増えました。 アドレッシングモードは32ビットとほぼ変わりませんが、以下の三点に注意する必要があります。

  • [imm32]がなくなり、代わりに[RIP + imm32]ができるようになった
    • [imm32 + index * scale]は残存
  • RSPの“裏レジスタ”であるR12は、ModR/MでRSPが使えないことを引き継いでいる
    • indexRSPが使えない制限は引き継いでいない
  • RBPの“裏レジスタ”であるR13は、(ModR/Mとbaseで)RBPでできないことを引き継いでいる
    • indexではRBPは自由に使える

つまり、直観的でない点は、

  • [RIP + index * scale + offset]はできない
  • [R12][R12 + (零)*1]で表現されるので1バイト長い
  • [R12 + offset][R12 + (零)*1 + offset]で表現されるので1バイト長い
  • [R13][R13 + 0]で表現されるので1バイト長い
  • [R13 + index * scale][R13 + index * scale + 0]で表現されるので1バイト長い
    • scale1の場合は、逆順にすると短くできることがある

また、エンコーディングの無駄な点(rex.b01かで意味が変化しない)として、

が挙げられます(まぁそもそもrex.xはSIBバイトを使わない場合、rex.bはメモリオペランドを取らない場合、rex.wはバイト命令の場合、は無意味なのですが)。

まとめ

  • 16ビットの時は、アドレッシングに使えないレジスタが存在した
  • 32ビット以降では、基本的にすべてのレジスタがアドレッシングに使える
  • しかし、特定のレジスタを使うアドレッシングは、命令長が伸びることがある
    • [BP][EBP][RBP]は1バイトのびる(しかし、フレームポインタとして使っているならばそこに入っているのはリターンアドレスなので、あまり使われない)
    • [EBP + index * scale][RBP + index * scale]は1バイトのびる(しかし、フレームポインタとして使っていて、indexが素直な配列インデクスであれば、このパターンは出てこないはず)
    • [ESP][ESP + offset][RSP][RSP + offset]のようにESPRSPがmodR/Mで指定されるとベースだと1バイトのびる(これはよく出てくる)
    • ESPRSPindexには使えない(使われることはないはず)
  • 64ビットでは、“裏レジスタ”の関係にあるレジスタEBPESPのModR/Mとbaseにおける特性を引き継いでいる
    • indexでは制約はない
    • [R12][R12 + offset][R13][R13 + index * scale]は1バイトのびる