GAS(ガス、ギャス、The GNU Assembler)とは、Dean Elsner, Jay Fenlasonが作成したアセンブリ言語である。Linuxカーネルの開発で利用されている。
アセンブラを学習することで、コンピュータ・アーキテクチャとプログラミングの概念を理解する。また最終的にバイナリクロックのGAS版を作成する。
# 1行のコメントはシャープ1つです。
/*
複数行のコメントはC言語などのように
スラッシュ、アスタリクスです。
*/
サンプル
.file "ファイル名.s" .data #ここに変数の定義を書く #データを書く .text #ここから実行文が始まる .globl main #はじめに呼び出される関数を.globlで指定(globalではなくglobl) main: # 命令を書く
データを.dataディレクティブ内で定義し、命令は.textディレクティブ内で定義する。
コーディング規約はない。
.file "helloworld.s" .data msg: .ascii "hello world\n" msgend: .equ len, msgend - msg .global main main: movl $4, eax # write system call(sys_write) movl $1, ebx # stdout movl $msg, ecx movl $len, edx int $0x80 ret
実行
$ gcc helloworld.s -o hw
$ ./hw
解説
eaxレジスタにsys_writeシステムコールを設定、引数が3つあるのでebxレジスタに出力先の標準出力を意味する$1を設定。第二引数にメッセージのデータ$msgを設定、第三引数にメッセージの長さの$lenを設定、最後にシステムコールのint $0x80を実行してシステムコールを呼び出している。またその次の行のretオペコードはreturnをあらわしこれによりプログラムが終了される。詳細は後ほど説明するので今は理解しなくてよい。
文法に入る前にCPUががどのようなものであるか分からなければ、アセンブラ開発が出来ないので、x86CPU年表、ニーモニック、オペコード、レジスタの説明をする。
x86CPU年表
| 年代 | CPU | bit | 概要 |
| 1971 | 4004 | 8 | |
| 1973 | 8080 | 16 | |
| 1978 | 8086 | 16 | |
| 1984 | 80286 | 16 | |
| 1985 | 80386 | 32 | |
| 1989 | 80486 | 32 | |
| 1993 | Pentium(i586) | 32 | Pentaは数字の5を意味する。この時から486などの数番からPentiumという言葉をつかう |
| 1994 | PentiumPro(i686) | 32 | 80686という位置付けだが、Pentiumから名前で呼ぶようになったためPentium Proといわれた。 |
| 1997 | MMX Pentium | 32 | MMX命令の追加 |
| 1997 | PentiumⅡ | 32 | オペコードの拡張とCPUの集積化(CPUの大きさを小さくすればそれだけ電力が少なくなるため、 |
| 1999 | PentiumⅢ | 32 | オペコードの拡張とCPUの集積化 |
| 2000 | Pentium 4 | 32 | 3Dグラフィックなどマルチメディア系命令がどんどん追加される。 |
| 2001 | Itenium | 64 | IntelとPentiumを文字って作られた用語。Intel初の64bitプロセッサ。サーバ用 |
| 2002 | Itenium2 | 64 | サーバ用、オペコードの拡張とCPUの集積化 |
| 2004 | Pentium-M | 32 | Pentium4の熱問題が原因で、新しく設計された省電力版Pentium |
Pentium4を例にとると、実際には、Pentium4も複数の種類が存在する。最初は開発コードWillamette-434(ウィラメット423)と呼ばれ、0.18μmで開発されたが、現在主流のPrescott(プレスコット)は90nmである。驚くかもしれないが、内部構造はインフルエンザウィルスよりも小さな物質での電流のやり取りが行われている。またPrescottは100W以上の電力を消費するため今後は製造が中止されることが決まっている。2004年第四半期ぐらいから、一般家庭用コンピュータにも64bitのIntelプロセッサが普及していくことが予想される。
興味のある方は以下のIntelサイトを確認してみてください。
ここで注意してみてもらいたいのは、1985年に製造された80386から現在のPentium4にいたるまでは、基本的な部分(32bit)は変わらないことである。IntelはCPUの集積化と命令の追加などをおこなってきた。つまり、80386を基本として学習をすれば現在のCPUのアーキテクチャも学習できるのである。
命令のことをニーモニックという。ニーモニックは、OPコード(オペコード)とオペランド(operand)のことをいう。
movb $0x01 %al
上記の例では、移動する命令のmovbがOPコード 、16進数の数値の$0x01とレジスタを意味する%alをオペランドという。
4004などの8bitCPUは、8bit(1byte)での処理を行っていたので、オペコードで8bitを処理をするにはbyteを意味するbをつける。
movb $0xff %ah
movb $0xff %al8086などの16bitCPUは、eXtend(拡張)を意味するレジスタを利用し、オペコードで16bitを処理するには16bitを意味するwordのwをつける。
movw $0x01ff %ax
386やPentiumなどの32bitCPUは、Extend(拡張)を意味するレジスタを利用し、オペコードで32bitを処理するには32bitを意味するlongのlをつける。
movl $0x123401ff %eax
これでハローワールドのmovオペコードの意味が理解できたと思う.また数値は$記号ではじまり、レジスタは%記号で始まる。
では、今までの説明を図にしてみる。
| eax(eXtend Accumurator eXtend) | |||
| ax(Accumurator eXtend) | |||
| ah(Accumurator Hi) | al (Accumurator Low) | ||
| 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
これで分かるように、x86CPUは4004などの命令も利用できる。つまり下位互換を持っている。最近のApple ComputerがG5を販売したが、これは64bitCPUをもっている。つまり32bitから64bitにあがることで、一度により大きな数値の計算を出来る。
この入門で利用するレジスタと役割を説明する。
汎用レジスタ
| レジスタ名 | 呼び名 | 役割 |
| eax | アキュムレータ | 計算につかう |
| ebx | ベースレジスタ | アドレスのベースにつかう |
| ecx | カウンタレジスタ | 反復処理のカウント |
| edx | 計算につかう | |
| esi | ソース | メモリ制御につかう |
| edi | デスティネーション | メモリ制御につかう |
| esp | スタックポインタ | メモリ制御につかう。一般にプログラマは、スタック領域の読み込みに利用 |
| ebp | ベースポインタ | メモリ制御につかう。一般にプログラマは、スタック領域の読み書きに利用 |
| eip | インストラクションポインタ | 次に行う命令が入る |
| eflags | フラグ | 比較、分岐などの際にこのフラグを確認する。 |
| cs | コードセグメント | コードがどこから始まるかメモリの場所を指定する |
| ds | データセグメント | データがどこから始まるかメモリの場所を指定する |
| es | エクストラセグメント | その他がどこから始まるかメモリの場所を指定する |
| ss | スタックセグメント | スタックがどこから始まるかメモリの場所を指定する |
| fs | ||
| gs |
helloworldのソースコードでどのレジスタを使ったか再確認しよう。
数値
10進数 $123, $-123
16進数 $0x12 $01212 $0x13ff55ee $-0xe1
2進数 $0b11110000
可変型(文字列)
msg: .ascii "hello world\n"
JavaでのString msg = "hello world\n"; C言語での char msg[13] = "hello world\n";と同様
文字
$'a' $'z'
データ定義の比較
| アセンブラ | C言語 | Java | |
| 1byte | cvar: .byte 0xff | char cvar = 0xff | byte cvar = 0xff |
| 2byte | wvar: .word 0xffff | short wvar = 0xffff | short wvar = 0xffff |
| 4byte | lvar: .loing 0xaaaaffff | long lvar = 0xaaaaffff | int lvar = 0xaaaaffff |
| 可変 | str: .ascii "hello" | char str[6] = "hello"; | String str = "hello"; |
| 可変 | wary: .comm 20,2 | short wary[10]; | short wary[10]; |
| 可変 | lary: .comm 20,4 | long lay[5] | int lary[5] |
.byte .word .long .asci. commなども.data .textと同様ディレクティブ
サンプル
.file "data_define.s" .data cvar: .byte 0xff wvar: .word 0xffff lvar: .long 0xaaaaffff str: .ascii "hello" wary: .comm ,20,2 # COMMon memory area (short wary[10]) .comm wary2,20,2 # same define lary: .comm ,20,4 # COMMon memory area (long lary[5]) .comm lary2,20,4 # same define .text .global main main:
演算子(+-*/%)などはない。すべてニーモニックで書く。
.file "authmetic.s"
.data
.text
.global main
main:
movb $0x00, %al
addb $0x0f, %al # 0x00 + 0x0f = 0x0f
subb $0x01, %al # 0x0f - 0x01 = 0x0e
movb $0x01, 0l
mulb 0l # %al * 0l = %ax
movw $0x00ff,%ax
movb $0x08, 0l
div 0l # %ax / 0l = %ahが商 %alが余り
ret
論理積(AND)
.file "and.s" .data .text .global main main: movb $0b00001111, %al andb $0b11110011, %al # = 0b00000011 = 0x03 ret論理和(OR)
.file "or.s" .data .text .global main main: movb $0b00001111, %al orb $0b11110000, %al # $0b11111111 = 0xff ret否定(NOT)
.file "not.s" .data .text .global main main: movb $0b11111111, %al not %al ret排他的論理和(XOR=eXclusive OR)
.file "xor.s" .data .text .global main main: movb $0b00111100, %al xor $0b11110000, %al # 11001100 ret否定論理積(NAND=Not AND)
.file "nand.s" .data .text .global main main: movb $0b00001111, %al andb $0b00001111, %al not %al ret否定論理和(NOR=Not OR)
file "nor.s" .data .text .global main main: movb $0b00001111, %al orb $0b11110000, %al not %al ret
分岐処理
.file "condition.s" .data stri: .ascii "condition\0" length: .equ len, length - stri .global main main: movb $5, %al cmpb $5, %al # alレジスタと数値5を比較する。比較結果はeflagsに格納される。 je f_print # もしequalだったらjump ret f_print: movl $4, eax movl $1, ebx movl $stri, ecx movl $len, edx int $0x80 ret
反復処理
.file "loopprint.s" .data msg: .ascii "hello\0" msize: .equ len, msize - msg .global main main: loop: movb $0, l incb l cmpb $10, l # compare 比較結果はeflagsに格納される。 jb loop # Jump if Below もし数が小さかったら ret
EFLAGSレジスタは32bitであるが、入門なので下位16bitのFLAGSレジスタのみ以下に表記する。
| EFLAGS | |||||||||||||||
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0(LSB) |
| システムフラグ | ステータスフラグ | コントロールフラグ | システムフラグ | ステータスフラグ | |||||||||||
| 0 | NT | IOPL | OF | DF | IF | TF | SF | ZF | 0 | AF | 0 | PF | 1 | CF | |
14 NT=Nested Task
13-12 IOPL=(I/O Privilege Level)
11 OF=Overflow Flag
10 DF=Direction Flag
9 IF=Interrupt Enable Flag
8 TF=Trap Flag
7 SF=Sign Flag
6 ZF=Zero Flag
5 予約
4 AF=Acxiliary carry Flag
3 予約
2 PF=Parity Flag
1 予約
0 CF=Carry Flagとりあえず、ステータスフラグのみ最初に覚えればよい。以下の時にフラグが立つ。(0から1になる。)
OF 演算結果がオーバフローした時
SF 符号がマイナス時
ZF 演算結果が0
AF 加減演算時に、ビット3番からの桁上がり(Carry)または桁借り(Borrow)時、BCDの時利用される。
PF 演算結果がのビット数が偶数個の時
CF 最上位ビットの桁上がりまたは桁借り時
.file "addressing.s" .data .text .file "addressing.s" .data .text .globl main main: # $0b000011111 $マークがイミディエイトオペランド # %でレジスタを指定しているのが、レジスタオペランド movb $0b00001111, %al # メモリオペランド movl -4(ebx), eaxメモリオペランドは、displacement(base, index, scale)の形で表記する。上記は省略形。メモリ上のデータアクセスにつかう
displacementは、byte(8),word(16),double word(32)の定数
baseは、0.000000E+00BXなどをつかう。
indexは、配列のインデックスと同等な意味
scaleは、スケーリングファクタといい、indexに倍率をかける。x1,x2,x4,x8が可能。
callオペコードとretオペコードで実装する。
.file "call_ret" .data .text .globl main main: movb $1, %al call test_func # jump test_func label call test_func ret test_func: incb %al # al++ ret関数に引数を設定する場合のサンプル
call by value 値渡し
.file "byvalue.s" .data .text .globl main main: movl $0x1111, eax push eax # eaxレジスタの実際の値をスタック領域に配置$0x1111->(esp) call func pop eax ret func: movl esp, ebp movl 4(ebp), edx incl edx retcall by reference 参渡し
.file "byref.s" .data var: .long 0x12121212 .text .globl main main: push $var # address into stack call func pop eax # dummy ret func: movl esp, ebp movl 4(esp), edx # copy address to edx register incl (edx) # refer value inclement var=0x12121213 ret
13番はtimeシステムコールを呼び出す。eaxに1-jan-1970からの秒数が入る。
.file "2c.s" .data .text .global main main: movl $13, eax movl $0, ebx int $0x80 ret80番でgettimeofdayが利用できるがアセンブラで構造体のポインタをどのように書くのか
分からないので調査中。
オーム社 プログラミングの力を生み出す本-インテルCPUのGNUユーザへ- ISBN4-274-13207-2