アセンブラ (nasm)

nasm のインストール

アセンブリ言語でプログラミングするためにはアセンブラが必要です。Linux 上で動作するアセンブラとしては GNU as(gas) と NASM が代表的です。 gas と NASMではソースの記述方法が異なり、gas は AT&T表記、NASM は Intel表記 となっています。AMDもインテルのマニュアルもIntel表記のため、nasm を使用する ことにします。NASM は https://nasm.sourceforge.net で配布されていますが、Ubuntu の場合ならメニューのシステム/システム管理/Synaptic パッケージマネージャ から「nasm」をインストールするか、アプリケーション/アクセサリ/端末 を起動して以下のコマンドでインストールしてください。

sudo apt-get install nasm

Ubuntu 8.04 には 0.99.06 がインストールされ、Ubuntu 8.10 では 2.03.01、Ubuntu 9.04 では現在の最新版 nasm-2.05.01 がインストールされます。0.99以降のバージョンならば x86-64 がサポートされています。

nasm の使い方

アセンブリ言語で書いたソース(myfile.asmとします)をアセンブルするには、 以下のコマンドを入力します。

nasm -f elf64 myfile.asm

ソースファイルの拡張子は自由な名称で構いません。出力されるオブジェクト ファイルのファイル名はソースファイルの拡張子が o に変更されたファイル名 (ここでは myfile.o)が生成されます。「-f elf64」は出力するファイル形式を 指定するオプションで、64bit Linux の場合は「elf64」を指定する必要があります。 空白を除いて「-felf64」でも問題ありません。

出力されるオブジェクトファイルの名前を指定する場合は以下のように「-o」 に続けて出力ファイル名を指定します。あいだの空白はなくても構いません。

nasm -f elf64 myfile.asm -o MyFile.obj

もうひとつ便利なオプションがあります。「-l myfile.list」など「-l」に続けてファイル名を入力するとアセンブル結果のリストが出力されます。

nasm -f elf64 myfile.asm -l myfile.list

オブジェクトファイルはリンクして実行形式に変換する必要があります。

ld -s -o myfile myfile.o

リンカ(ld)の出力ファイル名も「-o」に続けて出力ファイル名を指定します。 あいだの空白はなくても構いません。

次の様にシェルスクリプトを書けば楽になります。

  #!/bin/sh
  nasm -f elf64 $1.asm -l $1.lst && ld -s -o $1 $1.o

このシェルスクリプトのファイル名を asm とした場合、 chmod +x asm として実行属性を設定しておきます。

これで asm myprog とすれば、myprog.asm が myprog という実行形式にアセンブルされます。リストが myprog.lst に出力されます。

実行して確認するには ./myprog と入力します。

Makefile を作成するまでもないでしょう。

アセンブリソース

nasm で書いたソースファイルを示します。"hello, world" を端末に表示するだけの プログラムです。セミコロン(;)から行末まではコメントになります。

;------------------------------------
; hello.s
;   nasm -f elf64 hello.s
;   ld -o hello01 hello.o
;   ./hello
;------------------------------------

bits 64
section .text
global _start

_start:
        xor     eax, eax
        mov     edx, eax
        inc     eax         ; sys_write (01)
        mov     edi, eax    ; stdout    (01)
        mov     dl, len     ; length    (13)
        mov     rsi, msg    ; address
        syscall
        xor     edi, edi    ; return 0
        mov     eax, edi
        mov     al, 60      ; sys_exit
        syscall

section .data
        msg     db      'hello, world', 0x0A
        len     equ     $ - msg

bits 64 行は64bitモードの指定、section .text行はELF形式の実行ファイルのプログラムを配置する .text セクションの指定、global _start の行はプログラムが実行を開始する場所の指定です。アセンブリソースの先頭部分に以上の3行を置きます。_start:はラベルでここからプログラムの実行が開始されます。section .dataの行以降は初期化が必要なデータ用のセクションで実行ファイルに含まれます。このソースでは使っていませんが、section .bssは初期化が不要なデータ用の領域で実行ファイルには含まれません。

リストファイルの内容は以下のようになります。

     1                                  ;------------------------------------
     2                                  ; hello.s
     3                                  ;   nasm -f elf64 hello.s
     4                                  ;   ld -o hello01 hello.o
     5                                  ;   ./hello
     6                                  ;------------------------------------
     7                                  
     8                                  bits 64
     9                                  section .text
    10                                  global _start
    11                                  
    12                                  _start:
    13 00000000 31C0                            xor     eax, eax
    14 00000002 89C2                            mov     edx, eax
    15 00000004 FFC0                            inc     eax         ; sys_write (01)
    16 00000006 89C7                            mov     edi, eax    ; stdout    (01)
    17 00000008 B20D                            mov     dl, len     ; length    (13)
    18 0000000A 48BE-                           mov     rsi, msg    ; address
    19 0000000C [0000000000000000] 
    20 00000014 0F05                            syscall
    21 00000016 31FF                            xor     edi, edi    ; return 0
    22 00000018 89F8                            mov     eax, edi
    23 0000001A B03C                            mov     al, 60      ; sys_exit
    24 0000001C 0F05                            syscall
    25                                  
    26                                  section .data
    27 00000000 68656C6C6F2C20776F-             msg     db      'hello, world', 0x0A
    28 00000009 726C640A           
    29                                          len     equ     $ - msg

青字の部分 がアセンブラが生成した x86-64 CPU が実行するバイナリコードになります。

NASM の特徴

nasm は GNU as より, DOS系のアセンブラに近く、MASM, TASM より単純なため 任意の1行を見れば、アセンブルされるマシン語がわかるようになっています。 強力なマクロ機能もあり、抽象化したコードの作成も可能です。

例えば次の例ではラベルの宣言が異なりますが、

    foo   equ  1
    bar   dw   2
          mov  ax, foo
          mov  ax, bar

この場合は ax には定数値が代入され、

          move ax, [foo]
          move ax, [bar]

この例では、ax には メモリの内容が代入されます。 つまり、メモリ参照時には必ず[角カッコ]で囲む必要があり、[foo] となって いれば foo の宣言が何であってもメモリ参照であることが確定し、 foo であれば定数として扱われます。

オペランドの記述(アドレッシングモード)も違いがあります。

MASM NASM
mov ax, table[bx] mov ax, [table + bx]
mov ax, es:[di] mov ax, [es:di]

また、NASM では宣言時の変数の型を保持しません。したがってサイズが あいまいな場合(メモリへの格納など)には明示的にサイズを指定します。 var dw 0 と宣言されている場合でも、mov var, 2 ではなく、 mov word [var], 2 とサイズを明記する必要があります。 同様に LODS, MOVS, STOS 等のサイズ指定の無い命令は使用できず、 LODSB, MOVSW, SCASD のようにサイズ指定された命令を使用します。

MASM NASM
TBYTE TWORD
ST(0), ST(1) st0, st1
stack db 64 dup (?) stack resb 64 (dup は未サポート)

マクロを除くと基本的に1行だけで生成されるコードが決まるように 文法が単純になっています。

マニュアルページ (man nasm) の文法に関する部分を訳したものを示します。 より詳細な解説はドキュメントを参照して下さい。

  SYNTAX
       この man page は nasm のアセンブリ言語の文法を完全に記述し
       ているわけではなく、他のアセンブラとの差の要旨を示している。

       gas と異なりレジスタ名の前の`%' は不要であり, 浮動小数点ス
       タックレジスタは st0, st1 等と参照される。

       浮動小数点命令は1オペランド形式と2オペランド形式を使用で
       きる。 そのため TO キーワードが用意されている。
       したがって、以下の様に記述するしても良いし、

                      fadd st0,st1
                      fadd st1,st0

       また、1オペランド形式を使用することもできる。

                      fadd st1
                      fadd to st1

       初期化されないメモリの確保には RESB, RESW, RESD, RESQ, REST
       擬似コードを用いる。 それぞれ1つのパラメータでバイト、
       ワード、ダブルワード、4倍ワード、10バイトワードを単位として
       確保する数を指定する。

       データの繰り返しを DUP で指定するDOS用のアセンブラと異なって
       次のように TIMES プレフィックスを用いる。

             message: times 3 db 'abc'
                      times 64-$+message db 0

       文字列 `abcabcabc' と全体の長さが64バイトになるように0がに続
       くデータが設定される。

       シンボルの参照は常にイミディエイト(すなわちシンボルのアドレス)
       と解釈される。 ただし角括弧を使うとそのアドレスのメモリの内容
       を示す。

                      mov ax,wordvar

       これは、変数 wordvar のアドレスがAXに設定される。

                      mov ax,[wordvar]
                      mov ax,[wordvar+1]
                      mov ax,[es:wordvar+bx]

       これらのどの形式でもメモリの内容が参照される。

       以下の形式は使用できない。

                      mov ax,es:wordvar[bx]
                      es mov ax,wordvar[1]

       ただし、セグメントレジスタ名を命令のプリフィックスとして用
       いることはでき、他の方法でオーバーライドできない LODSB の
       ような命令に使われる。

       定数はほとんどの数値形式で表現できる。つまり H, Q, B を後ろ
       に付けて、16進数, 8進数、2進数としたり、0x か $ を前において
       16進数を表すことができる。
       文字定数はシングルクォートかダブルクォートで囲み、エスケープ
       文字はない。 並びはリトルエンディアン(逆順)であり、'abcd' と
       いう文字定数は 0x64636261 であって 0x61626364 ではない.

       ローカルラベルは名前がピリオドから始まるラベルであり、局所
       性は直前のローカルでないラベル以降となる。つまり `label' と
       いうラベルの後ろで `.loop' というローカルラベルを宣言した
       場合、実際には `label.loop' と宣言されたものとして扱われる.

   DIRECTIVES

       「SECTION 名前」または 「SEGMENT 名前」 はすべての後に続く
       コードは名前のつけられたセクションに属するものとして name
       に指定することになる。
       セクション名は出力ファイルの形式に依存するが、ほとんどの形
       式で .text, .data, .bss をサポートしている.  (例外は obj
       形式で, すべてのセグメント名はユーザ定義可能である.)

       「ABSOLUTE アドレス」は概念的なアセンブリ位置をその絶対アド
       レスに指定する。コードもデータも生成されないが、RESB, RESW,
       RESD を使ってアセンブリ位置を進めることができ、ラベルを定義
       することができる。この擬似命令をデータ構造の定義に使
       用することができる。絶対アドレスの指定を終わらせるために、
       別のセクションディレクティブを指定して通常の状態に戻さなけ
       ればならない。

       BITS 16 と BITS 32 またはBITS 64 は nasm が生成するプロセッサ
       のデフォルトモードを切り替える。つまりDOS用のアセンブラの 
       USE16とUSE32に相当する.

       「EXTERN シンボル名」と 「GLOBAL シンボル名」はシンボル定義
       をそれぞれ別のモジュールからインポートするか、別モジュールに
       エクスポートする。 GLOBAL ディレクティブはシンボルが定義され
       る前に置く必要があることに注意すること。

        「STRUC  strucname」と ENDSTRUC をいくつかの RESB, RESW など
       の命令を囲むように使うとデータ構造を定義できる。
       構造体メンバのオフセットを定義することに加えて、この構造は
       構造体名に _size を付加した構造体の大きさのシンボルを定義
       する。

続く...