アセンブリ言語　strlenを書いてみる

# アセンブリを書いてみる

アセンブリ言語はOSごとに異なっており、リファレンスも検索性が高いものがありません。
まともにリファレンスの情報をベースに実装していくのはとても困難な作業です。既存のまとめの継ぎ接ぎになってしまった感は否めなせんが、自分のメモとして残します。

主に参考にしたのは以下のサイトです。

https://www.mztn.org/lxasm64/amd00.html

### 環境

```
$ uname -a
Darwin user-pc.local 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64

$ sw_vers
ProductName:	macOS
ProductVersion:	11.2.3
BuildVersion:	20D91

$ nasm -v
NASM version 2.15.05 compiled on Aug 29 2020
```

## 書いたコード

追記 2021/04/23 プロローグ、エピローグの処理を消去

```nasm
section .text
	global _ft_strlen
_ft_strlen
	xor		rax, rax
.loop:
	cmp		BYTE [rdi + rax], 0
	je		.end
	inc		rax
	jmp		.loop
.end:
	ret
```

## セクション

```nasm
section .text
    global _ft_strlen
```

[セクションとか.textとか](http://www.ertl.jp/~takayuki/readings/info/no02.html)



## 引数の受け取り

引数の受け取りはレジスタを通して行われます
どのレジスタにどの引数が格納されるかは呼び出し規約というルールで定められているようです。

[x86 calling conventions - Wikipedia](https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI)

引数| レジスタ
-|-
第1引数 | RDI
第2引数 | RSI
第3引数 | RDX
第4引数 | RCX
第5引数 | R8

今回はstrlenなので第1引数のみ使用しています。

## xor rax, rax

raxというレジスタに0を入れています。
以下のように書くこともできますが、高速化のためにxorを使っています。

```
mov rax, 0x0
```

[c++ - bitの初期化は「0代入」と「^(XOR)演算」どちらが速い？ - スタック・オーバーフロー](https://ja.stackoverflow.com/questions/34839/bit%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%AF-0%E4%BB%A3%E5%85%A5-%E3%81%A8-xor%E6%BC%94%E7%AE%97-%E3%81%A9%E3%81%A1%E3%82%89%E3%81%8C%E9%80%9F%E3%81%84/34841)


やっていることとしては、`int i = 0;`みたいなイメージです。

### raxとは

汎用レジスタであり、64bitの値を扱うことが可能。
システムコール番号の指定にも使用され、結果が返る場所でもある。

変数的なものと思えばOKです。

## cmp  BYTE [rdi + rax], 0x0

```
cmp		BYTE [rdi + rax], 0x0
je		.end
```

rdi + rax のポインタからBYTE分(1byte分)、0x0と比較し、0x0と等しかった場合、_endにジャンプする。

大かっこでくくるとポインタを表すようになります。

### BYTE

参照するアドレスの範囲を指定しています。
指定する範囲は以下のようになります。

指定 | バイト数
- | -
BYTE | 1 バイト
WORD | 2 バイト
DWORD | 4 バイト
FWORD | 6 バイト
QWORD | 8 バイト

### cmp

レジスタとレジスタ、レジスタとメモリ内容、レジスタと定数を比較して、結果をフラグに反映させます。
内部的には左側のオペランドから右のオペランドを減算しますが、減算結果は返しません。
通常、条件ジャンプ命令が続きます。

### je		.end

真だったら(1だったら).endにジャンプ

### 条件ジャンプ命令

条件ジャンプ命令には様々な種類があります。

命令 | ジャンプ条件
-|-
JA | より上（CF = 0 & ZF = 0）
JAE | より上か等しい（CF = 0）
JB | より下（CF = 1）
JBE | より下か等しい（CF = 1 | ZF = 1）
JC | キャリーがある（CF = 1）
JCXZ | CXレジスタが0
JE | 等しい(ZF = 1）
JG | より大きい（ZF = 0 & SF = OF）
JGE | より大きいか等しい（SD = OF）
JL | より小さい（SF ! OF）
JLE | より小さいか等しい（ZF = 1 | SF ! OF）
JNA | より上でない（CF = 1 | ZF = 1）
JNAE | より上でなく等しい（CF = 1）
JNB | より下でない（CF = 0）
JNBE | より下でなく等しい（CF = 0 & ZF = 0）
JNC | キャリーがない（CF = 0）
JNE | 等しくない(ZF = 0）
JNG | より大きくない（ZF = 1 | SF ! OF）
JNGE | より大きくなく等しくない（SF ! OF）
JNL | より小さくない（SF = OF）
JNLE | より小さくなく等しくない(ZF = 0 & SF = OF）
JNO | オーバーフローがない（OF = 0）
JNP | パリティがない（PF = 0）
JNS | 符号がない（SF = 0）
JNZ | ゼロではない（ZF = 0）
JO | オーバーフローがある（PF = 1）
JP | パリティがある（PF = 1）
JPE | パリティが偶数(PF = 1）
JPO | パリティが基数(PF = 0）
JS | 符合がある(SF = 1）
JZ | ゼロである(ZF = 1）

一覧は以下から引用しました。
[インラインアセンブラで学ぶアセンブリ言語 第3回 (1/3)：CodeZine（コードジン）](https://codezine.jp/article/detail/485)

## inc

```
inc		rax
```

インクリメントです。
`i++`と同じです。ここではraxの値をインクリメントしています。

## jmp

```
jmp		.loop
```

_loopにジャンプします。

## ret

```
ret
```

retは `pop rip`と同じ意味

呼び出し元に戻るということです。

## 参考

今回は使っていませんが、インテルのアセンブリに関しては以下の資料集が参考になリます。

https://gist.github.com/tenpoku1000/2250ec65264ff2d639ddeeffd305fe68


リファンレスとしては以下のintelのものが最も適切かと思いますが、PDFのみで検索性も低く使いにくいです。

https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf


FreeBSDに固有な実装もあり、リファレンスを探しても見つからないものは以下のサイトを参照したりもしました。

[Chapter 11. x86 Assembly Language Programming](https://docs.freebsd.org/en_US.ISO8859-1/books/developers-handbook/x86.html)

ここはある程度検索できるので便利でした。
[Microsoft マクロアセンブラーリファレンス | Microsoft Docs](https://docs.microsoft.com/ja-jp/cpp/assembler/masm/microsoft-macro-assembler-reference?view=msvc-160)