自分で書いた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
を使っている、というのが真相でした。