初めに
assemblyを読めるようになりたい、ということで入門してみた。
コメントアウト
;でコメントアウトを開始する
mov eax, ebx ;eax を ebx に格納
メモリ構成
メモリは以下の領域からなる
- プログラム領域: 実行命令を機械語に変換したものが格納される
- データ領域: データが格納される
int x = 3;のような - ヒープ領域: 動的確保領域
- スタック領域: 関数呼び出し時の引数、戻り値アドレス、ローカル変数など
レジスタ
変数に相当するもの。CPUごとに個数が制限されている。
CPUの内部にあり、メモリよりも高速に動作する。
データの記憶だけでなく、制御にも使用する。
以下はx86の場合、
-
EAX:主に演算結果や返り値 -
EBX:汎用レジスタ(特定の用途に固定されていない) -
ECX:rep命令などループ回数で使われやすい -
EDX:割り込み・I/Oや乗算/除算の一部で利用 -
ESI/EDI:文字列操作系(rep movsb)で使われる -
EBP:関数のスタックフレーム基準 -
ESP:スタックポインタ
フラグ
CPUが算術演算や論理演算の結果を記憶しておくための特殊なビット。
以下の様なものがある。
-
ZF:計算結果が0の場合に立つ -
CF:桁溢れのときに立つ -
SF:計算結果がマイナスの場合に立つ -
OF:オーバーフロー時に立つ
CF と ZFの違い
CF は2進数で見たときに、桁があふれる場合
ZF は10進数で見たときに、桁があふれる場合
桁溢れ
基本文法
_start:
mov ebx, 0
mov ecx, 1
プログラムは_startラベルから始める
ほかにもラベルを定義できる
loop:
_startは慣例的にエントリポイントとして使われる
ニーモック
アセンブリの各命令をニーモックと呼ぶ。
オペラント
ニーモックの引数、対象を指定する。
x86では主に次の3種類がある。
-
即値(Immediate)
その場に書かれた数値
例:mov eax, 10 -
レジスタ(Register)
レジスタを指定
例:add eax, ebx
- メモリ参照(Memory Operand)
メモリアドレスを指定
例:mov eax, [ebx]
[] はメモリアドレスを表す
mnemonic operand1 operand2 ...
各命令はニーモックと0個以上のオペラントを記述する。
基礎ニーモック
データ転送
-
mov o1, o2
o1 に o2 を代入する
例:mov eax, ebx -
lea o1, [o2]
o2 が指すメモリアドレスを計算し、そのアドレスを o1 に格納する
メモリを読まずにアドレス計算のみ行う
例:lea eax, [ebx + ecx*4]eax に ebx + ecx * 4の計算結果が格納される
-
push/pop o1
スタックに値を積む、読む
例:push eax ; ESP -= 4, [ESP] = EAX pop ebx ; EBX = [ESP], ESP += 4
算術系
-
add o1, o2
o1にo2を加算する
例:add eax, 3 add eax, [ebx] -
sub o1 o2
o1にo2を減算するsub eax, 3 -
mul/imul o1, (o2)
乗算する
オペラントが2つの場合
o1 = o1 * o2
オペラントが1つの場合
eax = eax * o1
mul は符号なし、imul は符号あり
例:imul ebx ;eax = eax * ebx imul eax, ebx -
div/idiv o1
除算する
div は符号なし、idiv は符号あり
0除算のときは#DE例外が発生
例:div ebx ;eax = eax / ebx -
inc/dec o1
インクリメント/デクリメントinc eax
論理演算
-
and o1, o2
論理積、結果はo1に格納される
例:and eax, ebx -
or o1, o2
論理和、結果はo1に格納される
例:or eax -
xor o1,o2
論理的排除和、結果はo1に格納される
例:xor eax, ebx -
not o1
ビット反転
例:not eax -
shl/shr o1, 02
ビットシフト(左/右)
例:shl eax, 1 -
sal/sar o1, o2
ビットシフト(左/右)
ただし符号ビットを維持するsar eax, 1 ; 負数ならMSBを保持
比較、分岐
-
cmp o1, o2
o1-o2を計算しフラグを立てる-
符号なし
条件 フラグ a > b CF = 0 & ZF = 0 a == b ZF = 1 a < b CF = 1 -
符号あり
条件 フラグ a > b SF = OF & ZF = 0 a == b ZF = 1 a < b SF != OF
例:
cmp eax, ebx -
-
test o1, o2
o1 & o2を計算しフラグを立てる条件 フラグ 共通ビットなし ZF = 1 最上位ビットが1 SF = 1 下位8bit の1の数が偶数 PF = 1 -
jmp o1
命令o1に移動する
例:jmp loop -
je/jne o1
ZFが1/0の時ならばジャンプするje label -
jg/jl/jge/jle o1
フラグをもとにo1へジャンプする-
jg
ZF = 0 かつ SF = OF(o1 > o2) -
jl
SF != OF (o1 < o2) -
jge
SF = OF (o1 >= o2) -
jle
ZF = 1 かつ SF != OF(o1 <= o2)
-
その他
-
call o1
サブルーチン呼び出し
現在のアドレスはスタックに保存 -
ret
スタックに保存されたアドレスに戻る
システムコール
syscall
または
int
OS の提供している機能を利用する。
EAXの値で機能を指定する。
例:
| eax | システムコール名 | 説明 |
|---|---|---|
| 1 | exit | プロセス終了 |
| 2 | fork | 子プロセス生成 |
| 3 | read | FD から読み込む |
| 4 | write | FD に書き込む |
| 5 | open | ファイルを開く |
| 6 | close | ファイルを閉じる |
| 8 | creat | ファイルを作る(open の簡易版) |
| 19 | lseek | ファイル位置を移動 |
出力
EAX: write を指定
EBX: ファイルでスクリプターを指定
0-stdin
1-stdout
2-stderr
ECX: 出力する文字列を指定
EDX: 出力する文字列の長さを指定
mov eax, 4 ;システムコール
mov ebx, 1 ;標準出力
mov ecx, msg ;文字列
mov edx, len ;長さ
syscall
Hello, World!
section .data
msg db "Hello, world!", 10 ;
msglen equ $ - msg
section .text
global _start
_start:
mov eax, 4
mov ebi, 1
mov eci, msg
mov edx, msglen
syscall ; Call the kernel
mov eax, 1 ; Exit システムコール
mov ebi, 0 ; 終了コード
syscall ; Call the kernel
section .data解説
データ領域にバイト列を置く
msg db "Hello, World!", 10
msg: ラベル(文字列の先頭アドレス)
db: バイト列を定義
"Hello World!": 文字列
10: 改行コード
msg:
48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0A
のようになる。
msglen equ $ - msg
equ: 定数定義
$: 現在のアドレス
msg: 先のラベルアドレス
実行方法
nasm -f elf32 xxx.asm -o xxx.o
ld -m elf_i386 xxx.o -o xxx
./xxx
参考文献
終わりに
次は実際に書いてみる。
~Thank you for rading~