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

# はじめに

この記事は低レイヤーについてほとんど知識がない人間がアセンブリ言語を学んでいく際のメモ書きです。学習の際には「低レベルプログラミング」という本を参考書にしています。今回は[前回](https://qiita.com/tomomiche11eQ/items/799b8714162e13431282)の続きとして、より複雑なアセンブリプログラムを書いてみて、その挙動をgdbで確認するということをやってみようと思います。

# 目次

- gdbによるデバッグ
    - 起動と設定
    - 実際にデバッグしてみる
        - ブレークポイント
        - レイアウトを変更する
        - ステップインする
        - メモリの内容を確認する
- レジスタの中身を出力するプログラム
    - `lea`命令
    - `sub`命令･`add`命令
    - `push`命令･`pop`命令
    - レジスタの中身を出力するプログラム

# gdbによるデバッグ
gdbはバイナリを逆アセンブリしたり、ブレークポイントを置いてデバッグすることができるツールである。

今後、アセンブリした結果の挙動確認やデバッグに使用する際に便利そうなので、簡単な操作だけ学んでみた。

## 起動と設定

起動は

```
gdb デバッグしたいバイナリファイル
```

で行える。

```
gdb
```

で起動して、後でバイナリファイルを指定することもできる。

起動するとこのような画面になる。

![gdb_screen1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/4a0e4d5f-0e21-af87-1886-cd154ab48d48.png)

起動後、バイナリファイルを指定する場合は

```
file デバッグしたいバイナリファイル
```

とする。

デフォルトではアセンブリ言語の表示がAT&T表記となっているが、ここでは参考図書にあわせるためIntel表記で表示させるように設定する。

```
set disassembly-flavor intel
```

私は毎回設定するのが面倒なので、`~/.gdbinit`に上記の内容を保存している。

## 実際にデバッグしてみる

はじめに[前回](https://qiita.com/tomomiche11eQ/items/799b8714162e13431282)書いた以下のコードをgdbでデバッグしてみる。

使用するバイナリファイルは以下のアセンブリプログラムをアセンブリしたものである。

```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
```

### ブレークポイント

まずは、ブレークポイントを置いてみる。

ブレークポイントはラベルに対して置くことができるので、今回は

```
break _start
```

で`_start`ラベルにブレークポイントを置く。

すると

![gdb_break_point.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/7c544049-9cf4-a276-a23c-0ac851df16ba.png)


のような出力結果になる。

次にデバッグをスタートさせる。

```
start
```

すると以下のように聞かれるが、`n`と答える。

```
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
```

スタートすると以下のようにブレークポイントで止まる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/fc1eddb4-f0d2-8bf8-c0da-cda6da60c400.png)

### レイアウトを変更する

次に、レジスタの内容とアセンブリコードを表示させるためにレイアウトを変更する。

```
layout asm
layout regs
```

とすると、以下のような画面になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/a2d6e0ff-400a-5777-44ee-de80342675f9.png)

### ステップインする

では、コードをステップインしてデバッグしてみる。

ステップインする方法は

```
si
```

でできる。

ステップインしていくと、レジスタの内容が随時更新されていって、実際にどのような値が入っていっているのかが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/897462f2-b25e-d560-33e6-f17ebd1f6408.png)

### メモリの内容を確認する

今回取り上げたhello, worldを出力するプログラムでは`write`システムコールを実行する際にレジスタ`rsi`にhello, worldが格納された`message`のアドレスが代入されている。

そこで、実際に`rsi`に格納されたアドレスのメモリにhello, worldという文字列が格納されているのか確認してみる。

メモリを確認するには`x /a`というコマンドを用いる。また、文字列として表示させるのとhello, world\nの13字を表示させるために

```
x /13ac rsiに格納されたアドレス
```

を実行する。私の環境では`rsi`に格納されたアドレスは0x402000であったので

```
x /13bc 0x402000
```

を実行した。これは1バイトごとに文字列として`0x402000`のアドレスに入っている内容を表示するコマンドである。

すると以下のように表示され、ちゃんとhello, worldが入っていることがわかる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457322/dcf628db-edd4-bfcf-adeb-7e7ae077363b.png)


# レジスタの中身を出力するプログラム

`rax`レジスタの値を16進数として出力するプログラムを作成する。

コードは以下の通りになる。

```asm:print_rax.asm
section .data
codes:
    db '0123456789ABCDEF'

section .text
global _start
_start:
    mov rax, 0x1122334455667788

    mov rdi, 1
    mov rdx, 1
    mov rcx, 64
    ; 4bitを16進数の1桁として出力していくために、
    ; シフト論理和(AND)によって1桁のデータを得る
    ; その結果は'codes'配列へのオフセットである。

.loop:
    push rax
    sub rcx, 4
    ; clはレジスタ(rcxの最下位byte)
    ; rax > eax > ax = (ah + al)
    ; rcx > ecx > cx = (ch + cl)
    sar rax, cl
    and rax, 0xf

    lea rsi, [codes + rax]
    mov rax, 1

    ; syscallでrcxとr11が変更される
    push rcx
    syscall
    pop rcx

    pop rax
    ; testは最速の'ゼロか？'チェックに使える
    ; マニュアルで'test'コマンドを参照
    test rcx, rcx
    jnz .loop

    ; exitシステムコール
    mov rax, 60
    xor rdi, rdi
    syscall

```

これを実行する前に、新しく出てきた命令として`sub`や`sar`、`lea`、`test`、`pop`、`push`命令の挙動を確認する。

順番が前後するが、まず`lea`命令について確認してみる。

## `lea`命令

`lea`命令は`mov`命令と`add`命令を足し合わせた命令である。また、`lea`命令には第二引数に`[]`記号が使われている。`lea`命令の挙動と`[]`にどのような意味があるのかを確認するために以下のプログラムで実行してみる。

```asm:lea_vs_mov.asm
section .data
codes:
    db '0123456789ABCDEF'

section .text
global _start
_start:
    ; rsi <- ラベル'codes'のアドレス(数値)
    mov rsi, codes ; 1⃣

    ; rsi <- 'codes'というアドレスから始まるメモリの内容
    ; rsiは8バイト長なので連続する8バイトが取られる
    mov rsi, [codes] ; 2⃣

    ; rsi <- ラベル'codes'のアドレス
    lea rsi, [codes] ; 3⃣

    ; rsi <- (codes+rax)から始まるメモリの内容
    mov rax, 0x8
    mov rsi, [codes + rax] ; 4⃣

    ; rsi <- codes+rax
    ; これは
    ; mov rsi, codes
    ; add rsi, rax
    ; と同じ意味
    lea rsi, [codes + rax] ; 5⃣

    ; exitシステムコール
    mov rax, 60
    xor rdi, rdi
    syscall

```

はじめに1⃣の挙動をgdbを使用して確認してみる。

```
mov rsi, codes
```

をステップインしてみるとレジスタの状態は以下のようになる。

```
┌─Register group: general────────────────────────────────────────────────────────────────────┐
│rax            0x0                 0                                                        │
│rbx            0x0                 0                                                        │
│rcx            0x0                 0                                                        │
│rdx            0x0                 0                                                        │
│rsi            0x402000            4202496                                                  │
│rdi            0x0                 0                                                        │
│rbp            0x0                 0x0                                                      │
│rsp            0x7fffffffd790      0x7fffffffd790                                           │
│r8             0x0                 0                                                        │
│r9             0x0                 0                                                        │
│r10            0x0                 0                                                        │
```

`rsi`を見ると`0x402000`が入っていることがわかる。

これは私の環境では`0x402000`に`codes`の内容である`0123456789ABCDEF`が入っているからである。

試しに

```
x /16cb &codes
```

を実行すると

```
(gdb) x /16cb &codes
0x402000:       48 '0'  49 '1'  50 '2'  51 '3'  52 '4'  53 '5'  54 '6'  55 '7'
0x402008:       56 '8'  57 '9'  65 'A'  66 'B'  67 'C'  68 'D'  69 'E'  70 'F'
```

となっていて、`codes`は`402000`と`402008`にはいっていることがわかる。

次に更にステップインして、2⃣

```
mov rsi, [codes]
```

を実行してみる。

するとレジスタは

```
┌─Register group: general────────────────────────────────────────────────────────────────────┐
│rax            0x0                 0                                                        │
│rbx            0x0                 0                                                        │
│rcx            0x0                 0                                                        │
│rdx            0x0                 0                                                        │
│rsi            0x3736353433323130  3978425819141910832                                      │
│rdi            0x0                 0                                                        │
│rbp            0x0                 0x0                                                      │
│rsp            0x7fffffffd790      0x7fffffffd790                                           │
│r8             0x0                 0                                                        │
│r9             0x0                 0                                                        │
│r10            0x0                 0                                                        │
```

のようになる。

この`0x3736353433323130`は`codes`の中身そのものである。

これを確認するために

```
x /xg &codes
```

を実行して`codes`の中身を8バイト16進数でみると

```
(gdb) x /xg &codes
0x402000:       0x3736353433323130
```

となっていて、`rsi`には`codes`の値が格納されていることがわかる。1つのレジスタは8バイト長なので、8バイト分のデータが入っている。

このように`mov`命令の際にシンボルに`[]`をつけると、そのラベルの中身を扱うということがわかった。

対して、`lea`命令での`[]`記号はどのような意味があるのか見てみる。

3⃣をステップインすると、レジスタは

```
┌─Register group: general────────────────────────────────────────────────────────────────────┐
│rax            0x0                 0                                                        │
│rbx            0x0                 0                                                        │
│rcx            0x0                 0                                                        │
│rdx            0x0                 0                                                        │
│rsi            0x402000            4202496                                                  │
│rdi            0x0                 0                                                        │
│rbp            0x0                 0x0                                                      │
│rsp            0x7fffffffd790      0x7fffffffd790                                           │
│r8             0x0                 0                                                        │
│r9             0x0                 0                                                        │
│r10            0x0                 0                                                        │
│r11            0x0                 0                                                        │
│r12            0x0                 0                                                        │
│r13            0x0                 0                                                        │
│r14            0x0                 0                                                        │
```

となった。これは1⃣のときの状態と同じである。

つまり、

```
mov rsi, codes
```

と

```
lea rsi, [codes]
```

は同じ挙動となり、`[]`には`mov`命令のときにはあったラベルの中身の値を参照するという意味はなくなっている。

次に4⃣をステップインしてみる。

するとレジスタの状態は

```
┌─Register group: general────────────────────────────────────────────────────────────────────┐
│rax            0x8                 8                                                        │
│rbx            0x0                 0                                                        │
│rcx            0x0                 0                                                        │
│rdx            0x0                 0                                                        │
│rsi            0x4645444342413938  5063528411713059128                                      │
│rdi            0x0                 0                                                        │
│rbp            0x0                 0x0                                                      │
│rsp            0x7fffffffd790      0x7fffffffd790                                           │
│r8             0x0                 0                                                        │
│r9             0x0                 0                                                        │
│r10            0x0                 0                                                        │
│r11            0x0                 0                                                        │
│r12            0x0                 0                                                        │
│r13            0x0                 0                                                        │
│r14            0x0                 0                                                        │
```

となっていて、`rsi`には`0x4645444342413938`が入っていることがわかる。


この`0x4645444342413938`は`0x402008`のアドレスに入っている数値、すなわち`89ABCDEF`である。

gdbで

```
x /xg 0x402008
```

を実行すると

```
(gdb) x /xg 0x402008
0x402008:       0x4645444342413938
```

```
x /8bc 0x402008
```

を実行すると

```
(gdb) x /8bc 0x402008
0x402008:       56 '8'  57 '9'  65 'A'  66 'B'  67 'C'  68 'D'  69 'E'  70 'F'
```

という結果が得られる。

最後に5⃣をステップインしてみると、レジスタの状態は

```
┌─Register group: general───────────────────────────────────────────────────────────┐
│rax            0x8                 8                                               │
│rbx            0x0                 0                                               │
│rcx            0x0                 0                                               │
│rdx            0x0                 0                                               │
│rsi            0x402008            4202504                                         │
│rdi            0x0                 0                                               │
│rbp            0x0                 0x0                                             │
│rsp            0x7fffffffe1f0      0x7fffffffe1f0                                  │
│r8             0x0                 0                                               │
│r9             0x0                 0                                               │
```

となる。

レジスタ`rsi`には`0x402008`が入っており、`codes`のアドレスに`rax`に入っている値`0x08`が加算された値が格納されていることがわかる。

これは

```
mov rsi, codes
add rsi, rax
```

に等しい。

## `sub`命令・`add`命令

`add`と`sub`命令はそれぞれ加算と減算命令である。

サンプルとして以下のプログラムをデバッグしてみる。

```asm:add_sub.asm
section .text
global _start
_start:
    mov rsi, 0x5
    add rsi, 0x2
    sub rsi, 0x4

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

gdbを用いてレジスタの様子を見ていくと

```
mov rsi, 0x5
```

ではもちろん以下のように`rsi`に5という値が入る。

```
┌─Register group: general───────────────────────────────────────────────────────────┐
│rax            0x0                 0                                               │
│rbx            0x0                 0                                               │
│rcx            0x0                 0                                               │
│rdx            0x0                 0                                               │
│rsi            0x5                 5                                               │
│rdi            0x0                 0                                               │
│rbp            0x0                 0x0                                             │
│rsp            0x7fffffffe1f0      0x7fffffffe1f0                                  │
│r8             0x0                 0                                               │
│r9             0x0                 0                                               │
```

そして

```
add rsi, 0x2
```

をステップインするとレジスタは以下のようになり、`rsi`に2が加算されていることがわかる。

```
┌─Register group: general───────────────────────────────────────────────────────────┐
│rax            0x0                 0                                               │
│rbx            0x0                 0                                               │
│rcx            0x0                 0                                               │
│rdx            0x0                 0                                               │
│rsi            0x7                 7                                               │
│rdi            0x0                 0                                               │
│rbp            0x0                 0x0                                             │
│rsp            0x7fffffffe1f0      0x7fffffffe1f0                                  │
│r8             0x0                 0                                               │
│r9             0x0                 0                                               │
```

同じように、

```
sub rsi, 0x4
```

をステップインするとレジスタは以下のようになり、4が減算されていることがわかる。

```

┌─Register group: general───────────────────────────────────────────────────────────┐
│rax            0x0                 0                                               │
│rbx            0x0                 0                                               │
│rcx            0x0                 0                                               │
│rdx            0x0                 0                                               │
│rsi            0x3                 3                                               │
│rdi            0x0                 0                                               │
│rbp            0x0                 0x0                                             │
│rsp            0x7fffffffe1f0      0x7fffffffe1f0                                  │
│r8             0x0                 0                                               │
│r9             0x0                 0                                               │
```

## `sar`命令, `shr`命令

`sar`命令と`shr`命令はどちらも指定されたbitだけ右シフトする命令である。

違いとしては

- `sar`命令:算術右シフト
- `shr`命令:論理右シフト

という、符号の考慮の有無がある。

具体的には

- `sar`命令:指定されたbitだけ右シフトし、最上位ビットが1であるときシフトした分だけ左を1で埋める
- `shr`命令:指定されたbitだけ右シフトし、シフトした分だけ左を0で埋める

となる。

これを確かめるために、以下のプログラムを実行してみる。

```sar_shr.asm
section .text
global _start
_start:
    mov rax, 0x8000000012345678
    sar rax, 4
    mov rax, 0x8000000012345678
    shr rax, 4

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

```
sar rax, 4
```

をステップインすると`rax`は`0x8000000012345678`から`0xf800000001234567`になる。

これはもとの値を右に4bitシフトすると右端の`8`がなくなり`0x800000001234567`になるわけだが、もとの値の最上位ビットが1(最上位バイトが`8`つまり`0b1000`)であるので、左端をシフトした4bit分1で埋める(`0xf`が左につく)。

対して

```
shr rax, 4
```

をステップインすると、`rax`は`0x8000000012345678`から`0xf800000001234567`になるだけである。

もとの値の最上位ビットが0のとき、(例えば上記プログラムで`mov rax, 0x00000000012345678`としたとき)`shr`と`sar`の挙動は同じなる。

## `push`命令, `pop`命令

`push`と`pop`命令は文字通りスタックにpushとpopを行う命令である。

一応、挙動を確認するために以下のプログラムを実行してみる。

```push_pop.asm
section .text
global _start
_start:
    mov rax, 0xffff0000ffff0000
    push rax
    pop rdi

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

これらをステップインして実行していってみると


```
push rax
```

を行った際には、レジスタの様子は以下のようになる。

```
┌─Register group: general───────────────────────────────────────────────────────────────────────────────────┐
│rax            0xffff0000ffff0000  -281470681808896  rbx            0x0                 0                  │
│rcx            0x0                 0                 rdx            0x0                 0                  │
│rsi            0x0                 0                 rdi            0x0                 0                  │
│rbp            0x0                 0x0               rsp            0x7fffffffe1d8      0x7fffffffe1d8     │
│r8             0x0                 0                 r9             0x0                 0                  │
```

`rsp`はスタックポインタで、`rax`の内容を格納したメモリのアドレスが入っている。

```
(gdb) x /2x $rsp
```

で`rsp`が指すメモリの中身を見てみると

```
0x7fffffffe1d8: 0xffff0000      0xffff0000
```

となってちゃんと格納されていることが確認できる。

```
pop rdi
```

をステップインするとレジスタは以下のようになり

```
┌─Register group: general───────────────────────────────────────────────────────────────────────────────────┐
│rax            0xffff0000ffff0000  -281470681808896  rbx            0x0                 0                  │
│rcx            0x0                 0                 rdx            0x0                 0                  │
│rsi            0x0                 0                 rdi            0xffff0000ffff0000  -281470681808896   │
│rbp            0x0                 0x0               rsp            0x7fffffffe1e0      0x7fffffffe1e0     │
│r8             0x0                 0                 r9             0x0                 0                  │
```

ちゃんと`rax`に入っていた値が`rdi`に格納され、`rsp`が指すアドレスも8byte少なくなっている事がわかる。

## レジスタの中身を出力するプログラム

最後に最初に述べていた以下のプログラムを実行してみる。

```asm:print_rax.asm
section .data
codes:
    db '0123456789ABCDEF'

section .text
global _start
_start:
    mov rax, 0x1122334455667788

    mov rdi, 1
    mov rdx, 1
    mov rcx, 64
    ; 4bitを16進数の1桁として出力していくために、
    ; シフト論理和(AND)によって1桁のデータを得る
    ; その結果は'codes'配列へのオフセットである。

.loop:
    push rax
    sub rcx, 4
    ; clはレジスタ(rcxの最下位byte)
    ; rax > eax > ax = (ah + al)
    ; rcx > ecx > cx = (ch + cl)
    sar rax, cl
    and rax, 0xf

    lea rsi, [codes + rax]
    mov rax, 1

    ; syscallでrcxとr11が変更される
    push rcx
    syscall
    pop rcx

    pop rax
    ; testは最速の'ゼロか？'チェックに使える
    ; マニュアルで'test'コマンドを参照
    test rcx, rcx
    jnz .loop

    ; exitシステムコール
    mov rax, 60
    xor rdi, rdi
    syscall

```

実行結果は

```
1122334455667788
```

となり、`rax`に入っている値が出力されていることがわかる。

まず、

```
mov rdi, 1
mov rdx, 1
mov rcx, 64
```

について

`rdi`と`rdx`はそれぞれ`write`システムコールを行う際のディスクリプタ(値は1であるので標準出力)と出力するデータの長さ(1文字)である。

そして`rcx`は出力したい`rax`を右シフトしてマスキングするために使用する。例えば`rcx`が40のとき、64(`rax`のビット長)に対して40bit右シフトして、残りのの24bitが出力されるというような使い方である。

続いて

```
push rax
sub rcx, 4
sar rax, cl
and rax, 0xf
```

という部分について

`push rax`は`rax`の初期値を毎ループで使用するためにスタックにプッシュしている。こうすることで`rax`が右シフトされても、また初期値を利用したいときにポップすることで`rax`の値を初期値にすることができる。

`sub rcx, 4`というのは`rcx`をループごとに4ディグリーズさせる。`rcx`の値の遷移は`60, 56, 52, 48, ...`となっていく。`rcx`は`rax`の特定の部分を出力する際のマスキングに使用するので、ループごとにマスキングされる部分が4bitずつ増えるイメージである。

そして`sar rax, cl`で実際に右シフトして`rax`をマスキングしている。`cl`は`rcx`の下位8bitである(ループで使用される`rcx`の値は0~60なので8bitで間に合う)。

最後に`and rax, 0xf`という部分は`rax`の下位4bitを抽出している。`0xf`は`0b0000000000001111`なので`and`を取ると下位4bitを取ってこられる。

続いて

```
lea rsi, [codes + rax]
```

という部分について、`rax`には`rcx`で指定した部分(下位{64-`rcx`}bitのうち下位4bit)が入っている訳だが、`.data`セクションで定義した`codes`を用いることで、その4bit分の値をcharに変換している。
例えば4bitの値が`7`である場合、`[codes+7]`で`codes`の8文字目が参照され、char型の7が`rsi`に格納される。

続いて

```
mov rax, 1
push rcx
syscall
pop rcx
```

という部分では

`mov rax, 1`で`write`システムコール番号を指定して、`syscall`で`write`システムコールを実行している。

ここで、`write`システムコールは実行される際に`rcx`を利用するため、`push rcx`と`pop rcx`で`rcx`の値をスタックに退避させている。

最後に

```
pop rax
test rcx, rcx
jnz .loop
```

の部分では

`pop rax`で`rax`を初期値に戻し、`test rcx, rcx`及び`jnz .loop`で`rcx`が0になっていなければ`.loop`に戻るということを行っている。