#はじめに
最近の PC やサーバ用 CPU は特別なものを除けば x64 とか AMD64 とか呼ばれる 64ビット CPU が主流です。しかし、スクリプト言語や Java などを使っている限り、そのことを実感できません。実際に実感するためには、やはりアセンブラが一番です。
ということでアセンブラを使って64ビット CPU で遊んでみました。試した環境としては、AWS で稼働している Ubuntu 14.04LTS です。具体的にはこんな感じです。
- Single core Intel Xeon CPU E5-2670 v2
- Host: ip-172-??-??-130 Kernel: 3.13.0-36-generic x86_64 (64 bit)
#アセンブラとは
アセンブラとは正確にはアセンブリー言語とか言います。CPU は機械語だけしか解釈しませんが、命令がその機械語に1対1に対応するような言語です。言い直せば、機械語をシンボルを使って記述する言語みたいな感じです。
Linux の C コンパイラ (gcc) には as というアセンブラが付いてきますが、ここでは nasm というアセンブラを使っています。理由は、けっこうしっかりしたマニュアル (英語ですが) が付いているのと、記述方法が as よりシンプルなためです。
##nasm のインストール
nasm のインストールですが、Ubuntu では apt-get で簡単にインストールできます。もし、gcc が入ってない場合は、gcc もインストールしておく必要があります。
$ sudo apt-get install nasm
予備知識
アセンブラは機械語と1対1に対応しています。したがって、アセンブラ (アセンブリー言語) を知っていても、CPU の構成とか機械語を知らないとプログラムは作れません。ここでそこまで書いていたら長くなってしまいますし、本題に入るまでに時間がかかってしまうので、それについては読者が知っているものとします。
#それでは Hello World から
アセンブラは機械語と1対1ですから、他の言語だと1行ですむようなプログラムもこんなに長くなります。
section .data ; データセクションの定義
message db 'Hello, World', 0x0a
length equ $ -message ; 文字列の長さ
section .text
global _start ; エントリーポイント
_start:
mov rcx, message ; 文字列の先頭アドレス
mov rdx, length ; 文字列の長さ
mov rax, 4 ; 出力(sys_write)
mov rbx, 1 ; ファイルハンドル(1 = 標準出力)
int 0x80 ; システムコール
mov rax, 1 ; sys_exit
mov rbx, 0 ; 終了ステータスコード
int 0x80 ; システムコール
下がその実行例です。
ubuntu@ip-172-??-??-130:~/workspace/nasm$ bin/hello
Hello, World
ubuntu@ip-172-??-??-130:~/workspace/nasm$
ここでメッセージを表示する部分は最初の int 0x80 です。これは何かというと、ソフトウェア割り込み命令ですね。0x80 番の割り込みを行うと rax で指定した番号に対応する OS で用意されているエントリーポイントへ飛んでいくというものです。
最後にも int 0x80 がありますが、こちらはプログラムを終了する (OS に制御を返す) ためのシステムコールです。ところで rax とか rbx とかは 64bit 汎用レジスタと呼ばれるもので、CPU 内部の演算に使用するための一時記憶用高速メモリみたいなものです。mov は move 命令というもので即値データやレジスタのデータなどを他のレジスタにコピーします。
CPU の命令や構造ですが、こちらがわかりやすそうです。
ビルド方法
このプログラムをアセンブル+リンクするには下のようにします。この例で、ソースファイル (.asm) は src/ に、中間ファイル (.o) は obj/ に、実行ファイルは bin/ に置くものとします。
nasm -f elf64 -o obj/$1.o src/$1.asm
ld -s -o bin/$1 obj/$1.o
##Linux System Calls
Linux のシステムコールですが、下のサイトに詳細が出ています。全部で 338 種類もあるようです。
#簡単な足し算
次に簡単な足し算をしてみます。今度は、8ビットレジスタ al を使っています。システムコールも 64bit レジスタでなく本来の 32bit レジスタを使っています。また、前の例 (Hello World) ではリンカに直接 ld を使っていましたが、今度は gcc から間接的に使うようにしています。そのため、エントリーポイントの名前は C プログラムと同じ main に変えました。