読者です 読者をやめる 読者になる 読者になる

借り初めのひみつきち

仮ブログです。

nasmのバグ?

一般にアセンブリ言語機械語は一対一に対応するとよく言われますが、実際にはそうとは限りません。

x86の場合は同じ命令に別の機械語が割り当てられているものが幾つか存在し、全く同じアセンブリ言語のソースでもアセンブラの気分次第で別の機械語になることがあります。また、同じ機械語でも逆アセンブルしたときの文脈によって命令が変化する場合があります。

よくあるものとしてはaxレジスタを対象とした演算命令やmov命令は他のレジスタを使った場合より1バイト短い専用形式を持っています。
また、定数の表記方法も条件を満たした場合は汎用の命令より短い機械語にすることができる場合があります。

axレジスタと定数の間のand命令(and ax, XXXX)の場合、次の3つの形式があります。

25 はaxレジスタを対象とした特殊形式、 83 /4 は定数が符号付き8ビット以内に収まる場合の特殊形式で、 81 /4 が汎用の正式な形式です。
/4 というのは Mod R/M バイトの reg フィールドをレジスタ指定ではなくオペコード拡張用として使って 4 を代入しろという意味で、 R/M フィールドが ax の場合は合成すると E0 になります。

ちなみに、32bit(eax)版はサイズプレフィックスを除外すると以下のようになります。

  • 25 xx xx xx xx (5バイト)
  • 81 /4(E0) xx xx xx xx (6バイト)
  • 83 /4(E0) xx (3バイト)

問題は 83 /4 の形式で、正式なソースは見つけることができなかったのですが、どうやら386の頃に新たに定義された命令で8086では定義されていない命令のようです。
同じグループに属するadd命令などは 81 /0 と 83 /0 の両方を8086からサポートしていたのでデコーダーの構造によっては同じように 83 /4 のand命令もデコードできる可能性がありますが、正式には存在していなかった命令のようです。

さて、nasmの話になりますが、最近のnasmは短いバイト数の命令を選択する最適化機能があります。
基本的には全く同じ動作の命令ならバイト数が少ないほうがいいのでこの機能自体は歓迎すべき機能です。

そこでnasmでand命令をアセンブルしてみると 83 /4 形式が選択されます。

8086の場合 83 /4 は不正な命令になるので 25 の方を選択してほしいのですが、 8086 モードを指定したりサイズを指定したりしてみましたが 83 /4 を頑なに使い続けました。
16bitの場合は 25 と 83 /4 のバイト数は同じですが、32bitの場合は 83 /4 が最も短い命令になるので、おそらく32bitのサイズで比重が設定されているのでしょう。

このためだけに他の最適化をすべて切るわけにも行かないので困ったものです。