Homebrewで入るNASMは古すぎて、aptで入るNASMとは挙動が違う

自作CPUエミュの動作確認をしているときに、Homebrewで入るNASMとaptで入るNASMの挙動が違うことに気がついた。

挙動の違いを見る

簡単なコードをアセンブルして違いを見る。

BITS 32
    org 0x7c00
    sub esp, 16

aptで入るNASM

Ubuntuにaptで入るNASMを使って、アセンブルした結果をhexdumpで確認する。

$ nasm sub-test.asm -o sub-test-linux
$ hexdump -C sub-test-linux
00000000  83 ec 10                                          |...|
00000003

0x83のopcodeは、メモリとレジスタの間で8bitの即値を操作する。 ModR/Mによってどんな操作をするのかが分かる。この場合はレジスタの値から8bitの即値を引いている。

Homebrewで入るNASM

macOSにHomebrewで入るNASMを使って、アセンブルした結果をhexdumpで確認する。

$ nasm sub-test.asm -o sub-test-mac
$ hexdump -C sub-test-mac 
00000000  81 ec 10 00 00 00                                 |......|
00000006

0x81のopcodeは、メモリとレジスタの間で32bitの即値を操作する。 この場合もModR/Mによってどんな操作をするのかが分かる。レジスタの値から8bitの即値を引いている。 オペランドに指定している0x16は8bitで収まるのに32bitの無駄に大きい値を扱っていて、うまくアセンブルできていないことがわかる。

バージョンの違いが原因だった

macOSでHomebrewを使ってインストールできる最新のNASMのバージョンは以下の通り。

$ nasm -v
NASM version 0.98.40 (Apple Computer, Inc. build 11) compiled on Aug 24 2016

Ubuntuでaptを使ってインストールできる最新のNASMのバージョンは以下の通り。

$ nasm -v
NASM version 2.10.09 compiled on Jun 28 2018

この通り、バージョンが大幅に違う。 appleが独自にメンテしているからバージョンの進み方が違うのかなと思ったけど、 単に古いだけっぽくて、最新のmacOS向けのビルドは以下のリンクで配布されている。 Index of /pub/nasm/releasebuilds/2.13/macosx

まとめ

このようにアセンブラのversionが違うと、出力されるopcodeが変わってくるので気をつけたい。