初心者がIntel x64アセンブリに入門する　その２

# はじめに

この記事は低レイヤーについてほとんど知識がない人間がアセンブリ言語を学んでいく際のメモ書きです。学習の際には「低レベルプログラミング」という本を参考書にしています。今回は[前回](https://qiita.com/tomomiche11eQ/items/a70e7dfdbcb54b07c498)に学習したレジスタの理解を前提として、実際にアセンブリ言語を書いていきたいと思います。

# 目次

- hello, world
- プログラムの中身
    - コメント
    - セクション
    - ラベル
    - ディレクティブ
    - ディスクリプタ
        - 基本的な命令
        - `mov`命令
        - `xor`命令
    - システムコール
        - `write`システムコール
        - `exit`システムコール

# 環境

- os： Ubuntu 22.04 LTS (WSL2)
```
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```

- アセンブラ

```
$ nasm --version
NASM version 2.15.05
```

# hello, world

とりあえず、はじめてのアセンブリプログラムということで、hello worldを出力してみる。

```hello.asm
section .data
message: db 'hello, world!', 10

section .text
global _start
_start:
    mov rax, 1       ; システムコールの番号をraxに入れる
    mov rdi, 1       ; 1は書き込み先(descriptor)
    mov rsi, message ; messageは文字の先頭
    mov rdx, 14      ; 14は書き込むバイト数
    syscall          ; システムコールの呼び出し

    mov rax, 60      ; 60は'exit'のsyscall番号
    xor rdi, rdi
    syscall
```

実行するには、

1. `nasm`によりアセンブリして
2. `ld`でリンク
3. 適切な実行権限を与える

という手順で実行できます。

```
$ nasm -felf64 hello.asm -o hello.o
$ ld -o hello hello.o
$ chmod u+x hello
```

実行結果は以下のようになるはずです。

```
$ ./hello
hello, world
```

# プログラムの中身

## コメント

アセンブリ言語ではセミコロンからその行の最後までがコメントとして扱われる。

## セクション

ノイマン型コンピュータでは1つのメモリにコードとデータが書き込まれる。プログラムを作成する際には、この2つを別々に扱うためにプログラムを複数のセクションに分ける。

`.text`セクションには命令、`.data`セクションにはグローバル変数(global variable)が記述される。

## ラベル

可読性を上げるためにプログラマはラベルを使用することができる。ラベルによってアドレスに読みやすい名前を付けることができる。

プログラムでは

```
_start:
```

がそれにあたるが、この`_start`というラベルはアセンブリプログラムのエントリーポイントであり、1つのアセンブリプログラムに必ず設けられるラベルである(1つのアセンブリプログラムを複数のファイルに分けて場合には、その中の1つに`_start`ラベルが設けられる)。

また、`_start`ラベルはglobal宣言が必要である。プログラム内でも

```
global _start
```

として宣言されている。

## ディレクティブ

global宣言、セクションなどの変数処理を制御するコマンドをディレクティブ(directive)と呼ぶ。

プログラムでは、先程の`global`や`section`の他に`db`というディレクティブも使用されていて、これはバイトデータを作成する際に使用される。

```
message: db 'hello, world!', 10
```

この`, 10`で`hello, world`にASCIIコードでの10の文字(改行`\n`)を足している。

データを作成する際は

- `db`: バイト(1byte)
- `dw`: ワード(2byte)
- `dd`: ダブルワード(4byte)
- `dq`: クアッドワード(8byte)

の4つのディレクティブが使用される。

## ディスクリプタ

`write`システムコールの出力先を決めるのがディスクリプタ(descripter, 記述子)である。これはファイルを識別する番号で、中身は単なる整数である。基本的にはディスクリプタによって指定されたファイルは`open`システムコールを呼び出し明示的にファイルをオープンしなければならないが、`stdin`と`stdout`、`stderr`の3つはプログラムが開始されると即座にオープンされ、プログラマ側が管理することはできない。それぞれのディスクリプタの値は以下の通りである。

- `stdin`: 0
- `stdout`: 1
- `stderr`: 2

また、デフォルトではキーボード入力は`stdin`に、ターミナル出力は`stdout`にリンクされている。

今回の場合、`hello, world`という文字列は標準出力に書き込まないといけないので、ディスクリプタの値は1とする。

プログラムでいうと以下がその部分に該当する。

```
mov rdi, 1
```

### 基本的な命令

### `mov`命令

`mov`命令はある値をレジスタやメモリに書き込むことができる。書き込む値は直接指定する以外に、他のレジスタやメモリから取ることもできる。

プログラムでは

```
mov rax, 1
```
などがそれにあたる。この場合、レジスタ`rax`に直接1を書き込んでいる。

### `xor`命令

`xor`命令は第一引数に対して第一引数と第二引数の排他的論理和をとって書き込む。

プログラムでは

```
xor rdi, rdi
```

がそれにあたる。`rdi`=xor(`rdi`, `rdi`)となっている。

## システムコール

システムコール(system call)は`syscall`により実行することができる。その際は`rax`に入っているシステムコール番号のシステムコールが実行される。

システムコールの引数を設定するには順に`rdi`、`rsi`、`rdx`、`r10`、`r9`に値を格納する。

今回のプログラムでは

```
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 14
syscall
```

という部分と

```
mov rax, 60
xor rdi, rdi
syscall
```

という部分がそれにあたる。

### `write`システムコール

`hello, world`という文字列を出力するには`write`システムコールを利用する。これは出力先が標準出力だろうが、標準エラー出力だろうが、ファイルだろうが同じである。そして`write`システムコールの番号は1であるためレジスタ`rax`に格納しておく。

プログラムでいうと以下がその部分にあたる。

```
mov rax, 1
```

### `exit`システムコール

プログラムを正常に終了するために`exit`システムコールを実行する。

プログラムでは

```
mov rax, 60
```

がそれにあたり、`rax`に`exit`システムコール番号である60を代入することで実行している。

そして、`exit`システムコールの引数は終了ステータスコードを指している。正常終了は0、一般的なエラーは1などである。

プログラムでは正常終了のステータスコードを`rdi`に格納するために

```
xor rdi, rdi
```

としている。排他的論理和は2つの値が同じ時、0になるので必然的に`rdi`には0が格納される。

つまり、

```
mov rdi, 0
```

とやっていることは同じになる。ただ、`xor`を使用することで命令が小さくなるため、`xor`を使用した書き方が一般的である。

ちなみにこの`exit`システムコールが実行されないとプログラムは次の命令(`write`システムコールが呼び出された後にメモリセルに入っているランダムな値を読み込んで実行される命令)が実行され、意図していない命令が実行され、クラッシュする。

`exit`システムコールを呼び出さないコードは以下のようになる。

```hello_without_exit.asm
section .data
message: db 'hello, world!', 10

section .text
global _start
_start:
    mov rax, 1       ; システムコールの番号をraxに入れる
    mov rdi, 1       ; 1は書き込み先(descriptor)
    mov rsi, message ; messageは文字の先頭
    mov rdx, 14      ; 14は書き込むバイト数
    syscall          ; システムコールの呼び出し
```

これを以下のように実行する。

```
$ nasm -felf64 hello_without_exit.asm -o hello_without_exit.o
$ ld -o hello_without_exit hello_without_exit.o
$ chmod u+x hello_without_exit
$ ./hello_without_exit
```

すると、実行結果は

```
$ ./hello_without_exit
hello, world
zsh: segmentation fault  ./hello_without_exit
```

となって、セグフォしていることがわかる。