自分で書いたC言語のコードを逆アセンブルしていたところ、妙なオペコードの存在に気づきました。
基本知識
NOP命令
多くのCPUには、「何もしない」命令であるNOP命令が備わっています。CPUによっては、設計時に「全ビット0」のようなパターンを意識的にNOPへ割り当てていることがあります。
x86はそうではなく、もともとはXCHG EAX, EAX(EAXレジスタ同士を交換するという、実質何も実行しない命令)をNOPとして使っていました。その後のパイプライン化でEAXレジスタを触らないように最適化されることとなり、x64に至っては(本来32ビットレジスタに触れば上位32ビットはクリアされるはずが)真に何もしない命令となっています。
また、このXCHGから派生したものとは別系統の、0f 1fから始まるオペコードのNOPも実装されています。
WORD PTR
AT&T記法ではmovlやandwというように、命令そのものでオペランド幅を表現するようになっています。一方、インテル記法の場合、命令自体はMOVやANDのようにオペランド幅を問わず共通となっています。
どこかのオペランドがレジスタなら、幅は決め打ちにできるのですが、メモリと即値の場合、何も書かなければ幅がわかりません。ということで、BYTE PTR、WORD PTR、DWORD PTR、QWORD PTRというものをメモリオペランドの前に補って、どんな幅でメモリをアクセスするのかを(アセンブラレベルで)表現します。
妙なオペコード
objdump -d -M intelとして、自分がCで書いたコードを逆アセンブルしていたのですが、その過程で妙なものを発見しました。
fa1: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
fa8: 00 00 00
なんと、NOP命令に複雑なオペランドを指定してあるのです。これはもしかして、「NOPと見せかけて何かしらの処理を行うのでは」なんてことも思ったのですが、調べてみればやはり「何もしない」とのことでした。
どうやら、これはアライメントを合わせるために埋めてあるものだということでした。そして、1バイトのNOPで埋めるよりデコードの効率がいいので(一度に○命令デコードできるとのこと)、オペランドやプレフィックスを指定して10バイトにしたNOPを使っている、というのが真相でした。