X86アセンブラ/基本的なFAQ

出典: フリー教科書『ウィキブックス(Wikibooks)』

このページでは、アセンブリ言語でのプログラミング初心者の基本的なFAQについて答えよう。

プログラムと機械語[編集]

hello.c
#include <stdio.h>

int main() {
    printf("Hello world\n");
}
これは、高級言語の1つC言語で書かれた、短く有名なプログラムです。

これをコンパイルして、結果を llvm-objdump というツールでどのような機械語に変換されるか見てみましょう。

amd64をターゲットとしたコンパイル
% clang -c -g -o hello hello.c
% llvm-objdump  -S hello

hello:  file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
; int main() {
       0: 55                            pushq   %rbp
       1: 48 89 e5                      movq    %rsp, %rbp
;     printf("Hello world\n");
       4: 48 8d 3d 00 00 00 00          leaq    (%rip), %rdi            # 0xb <main+0xb>
       b: b0 00                         movb    $0, %al
       d: e8 00 00 00 00                callq   0x12 <main+0x12>
; }
      12: 31 c0                         xorl    %eax, %eax
      14: 5d                            popq    %rbp
      15: c3                            retq
Cで書かれたソースコードの間に
       0: 55                            pushq   %rbp
のようにソースコードになかった行があるのがコンパイル結果の機械語(55)とそのアセンブリ言語の表記(pushq %rbp)です。

この例は、amd64 を対象にコンパイルしているので、機械語およびアセンブリ言語の表記は amd64 のものですが、異なるプロセッサでは結果も異なります。

arm64をターゲットとしたコンパイル
% clang -c -g -o hello hello.c
% llvm-objdump  -S hello

hello:  file format elf64-littleaarch64

Disassembly of section .text:

0000000000000000 <main>:
; int main() {
       0: a9bf7bfd      stp     x29, x30, [sp, #-16]!
       4: 910003fd      mov     x29, sp
       8: 90000000      adrp    x0, 0x0 <main+0x8>
       c: 91000000      add     x0, x0, #0
;     printf("Hello world\n");
      10: 94000000      bl      0x10 <main+0x10>
      14: 2a1f03e0      mov     w0, wzr
; }
      18: a8c17bfd      ldp     x29, x30, [sp], #16
      1c: d65f03c0      ret
同じソースコードからのコンパイル結果ですが、プロセッサが違うと全く結果が違うことがわかります。

このように、Cのような高級言語を使うことで、プロセッサが違ってもプログラミング上は意識しなし、あるいは意識することが少なくなります。

コンピュータはアセンブリ言語をどのように読んで理解するのか?[編集]

コンピュータは実際のところ読みも、理解も、何もそれ自体ではしないが、しかしこういう答えが求められている訳ではないだろう。事実は、コンピュータはあなたの書いたアセンブリ言語を読むことはできないということである。アセンブラがアセンブリ言語を機械語と呼ばれる2進数の情報の形に変換し、コンピュータはそれを使って動作するのである。コードがアセンブルされていないならば、それはコンピュータにとって全く意味不明である。

アセンブリ言語のそれぞれの命令は、通常ただ一つの機械語に対応しているので白紙と鉛筆とアセンブリ命令のリファレンスブックだけがあれば、「ごく普通の人」でもこの作業(ハンドアセンブル)を実行することができる。実際、コンピュータが使われだした最初の頃は、これは一般的にされていたことであって、基本的なコンピュータプログラムのいくつかにとっては「ハンドアセンブル」するというのは必要なことであった。このころの古典的な例としては、スティーブ・ウォズニアックによるものがある。彼にとっての最初のApple Iコンピュータで使うために、Integer BASICインタプリタの全てを6502機械語にハンドアセンブルした。しかし、覚えておいてほしいのだが、商業的に配布されたソフトウェアのためにこのような作業がされたのは、非常にめずらしいことであるから話題になるのである。本当にほんの少しのプログラマが、実際に少数の命令についてハンドアセンブルをした経験を持っているにすぎないし、経験があるとしても学校の宿題としてやったことがあるに過ぎない。

WindowsとDOSとLinuxでアセンブリ言語は同じなのか?[編集]

この質問への答えはイエスであり、ノーでもある。基本的なx86機械語はプロセッサにのみ依存する。x86バージョンのWindowsとLinuxは明らかにx86機械語で作られている。x86アセンブリ言語でのプログラミングはLinuxとWindowsとで少し異なっている。

  1. Linuxが動作している環境では、最も一般的なアセンブラはAT&T記法を採用するGASアセンブラか、MASMに似た記法を採用するNASMという名前でも知られるNetWideアセンブラである。
  2. Windowsが動作している環境では、最も一般的なアセンブラはMASMであり、インテル記法が採用されている。
  3. 使用可能なソフトウェア割り込みとその機能はWindowsとLinuxで異なっている。
  4. 使用可能なライブラリはWindowsとLinuxで異なっている。

同じアセンブラを使うならば、基本的なアセンブリコードはオペレーティングシステムによらず基本的には同じである。 しかし、Windowsと連携するならば、Linuxと連携する場合とコードは異なったものとなる。

どのアセンブラがベストか?[編集]

簡単に答えると、どのアセンブラも他のどのアセンブラよりも似たり寄ったりであり、個人の好みの問題である。

もう少し詳しく答えると、アセンブラはそれぞれの利点と欠点がある。あなたがGAS構文しか知らないとしたら、あなたは多分GASを使いたがるであろう。インテル文法を知っておりWindowsマシンで仕事をしているのであれば、MASMを使いたくなるかもしれない。MASMやGASの癖や複雑さが好きでないならば、FASMやNASMを使いたくなるかもしれない。この本では第2節でそれぞれのアセンブラの違いを扱う。

アセンブリ言語を知る必要があるか?[編集]

ほとんどのコンピュータに関連する作業をするためにアセンブリ言語を知る必要はないが、知っておけば役に立つ。アセンブリ言語を学ぶことは、新しいプログラミング言語を学ぶということではない。新しくプログラミングを始めようとするとき(プロジェクトがブートローダやデバイスドライバ、カーネルでなければ)、アセンブリ言語はやっかいものとして避けられるだろう。例外は、絶対的にパフォーマンスが重要であったり、コンパイラが最適でないコードを生成する場合である。しかし覚えておいてほしいのは、不完全な最適化は全ての悪の根源である。ただし、最適化技術が理解されており最初から計画されていれば、計算量が重要となるリアルタイムなタスクでは簡単に十分な最適化が実施できる。

しかし、アセンブリ言語を学ぶことによってコンピュータがその内部でどのように働いているのかについての詳細な見識を得ることができる。C言語やAdaといった高水準言語でプログラムを書いても、コードは全て最終的にはコンピュータの実行できる機械語の命令に変換される必要がある。プロセッサが何ができるかについて正確に理解することは、最も基本的なレベルにおいて、高水準言語でプログラミングをする際の助けとなる。

コードにはどのようにフォーマットを使うべきか?[編集]

ほとんどのはアセンブリコードの命令は、それぞれを各行に書き改行で分けている。また、ホワイトスペースが命令、オペランドなどを分けるのに使われる。正確に言えば、コードにどのようなフォーマットを採用すべきかは、あなたにまかされている。しかし、一般的な方法というものはいくつかある。


一つは全てを1行ずつ書く方法である。

Label1:
mov ax, bx
add ax, bx
jmp Label3
Label2:
mov ax, cx
...

もう一つは、ラベルを最初の列に書き、命令をそれ以降の列に書く方法である。

Label1: mov ax, bx
        add ax, bx
        jmp Label3
Label2: mov ax, cx
...

ほかにも、ラベルで1行を使い、命令を少しインデントして書く方法もある。

Label1:
   mov ax, bx
   add ax, bx
   jmp Label3
Label2:
   mov ax, cx
...

これらに加えて、ラベルを最初の列に書き、命令を以降の列に書くが、ラベルには別の行を使う方法もある。

Label1:
        mov ax, bx
        add ax, bx
        jmp Label3
Label2:
        mov ax, cx
...

本当にたくさんの書き方があるが、アセンブリプログラマが一般的に従っているルールがいくつかある。

  1. ラベルは、他のプログラマがどこにラベルがあるかはっきり分かるように書く。
  2. 構造(インデント)はコードが読みやすいようにする。
  3. コメントを使い、何をしているのか説明する。アセンブリ・コードの意味は、すぐにははっきりしないことが多いからである。

インラインアセンブラ[編集]

※ 日本版独自の節です。

アセンブラはC言語に組み込んで使うこともできる。このように、C言語プログラムに組み込んで使うアセンブラのことをインライン アセンブラ( Inline assembler)という。

C言語コンパイラの種類によってインライン アセンブラの呼び出し方の文法が少々違う。インライン アセンブラについて、詳しくは Wikibooks『C言語/おわりに』で説明する。

本書では、インラインアセンブラには深入りしない。