コンピュータと低レベルなじゃんけんをする

# 概要
やっほーtrimscash dayo~
この記事では低レベルなじゃんけんプログラムを見せ，linuxのx86_64のsyscallでの標準入出力，rdrandの適当な解説していくよ～
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/d441015e-f595-76c9-be6c-916357c7ad36.png)

# プログラム
gccでコンパイルだけしてldリンクしています
なんか変なとこあったらおしえてくだせえ，，先に謝っておくごめんなさい．

```ruby:janken.s
.intel_syntax noprefix
.global main

main:
	lea r11, _descriptionText
	push r11
	call print
	pop r11#r11 ni butikonderu noha nantonaku 
	
	lea r11, _jankenStartText
	push r11
	call print
	pop r11

	call getChar#rax will be destroyed
	xor r14, r14
	mov r14,rax
	sub r14, 0x30
checkHand:
	cmp r14, 2
	jle checkHand_1
	jmp exception
checkHand_1:
	cmp r14, 0
	jge checkHand_end
	jmp exception
checkHand_end:
	push r14
	call printHand
	pop r14

	call getRand#RIP rax
	mov r15, rax
	push r15
	call printHand
	pop r15

judge:	
	sub r14, r15
	mov rax, r14
	mov rdx, 0
	add rax, 3
	mov r11,3
	div r11#rdx..0 draw 1 lose 2 win
drawJudge:
	cmp rdx, 0
	jnz loseJudge
	lea r11, _draw
	push r11
	call print
	pop r11
	jmp endJudge
loseJudge:
	cmp rdx, 1
	jnz winJudge
	lea r11, _lose
	push r11
	call print
	pop r11
	jmp endJudge
winJudge:
	cmp rdx, 2
	jnz exception
	lea r11, _win
	push r11
	call print
	pop r11
	jmp endJudge
exception:
	lea r11, _exception
	push r11
	call print
	jmp endJudge
endJudge:
	jmp exit

#end main######################

#print hand
#(int(0~2))
printHand:
	push r15
	push r14
	push rbp
	mov rbp, rsp
	mov r15, [rbp+32]
printHand_case_0:
	cmp r15,0
	jnz printHand_case_1
	lea r14, _GU
	push r14
	call print
	pop r14
	jmp printHand_end
printHand_case_1:
	cmp r15, 1
	jnz printHand_case_2
	lea r14, _CHOKI
	push r14
	call print
	pop r14
	jmp printHand_end
printHand_case_2:
	cmp r15, 2
	jnz printHand_end
	lea r14, _PA
	push r14
	call print
	pop r14
	jmp printHand_end
printHand_end:
	pop rbp
	pop	r14
	pop r15
	ret


#destory rax
getRand:
	push r11
	push rdx
	mov rax, 0
	mov rdx, 0
	rdrand rax
	mov r11,3
	div r11
	mov rax, rdx
	pop rdx
	pop r11
	ret

#destroy rax
#input char to rax
getChar:
	push rcx
	push r11
	mov rax, 0#read
	mov rdi, 0#stdin
	lea r11, [_input]
	mov rsi, r11
	mov rdx, 2 
	syscall
	xor rax,rax
	movb al, _input
	pop r11
	pop rcx
	ret


#print string
#(string_adr)
print:
	push rax
	push r11
	push rcx
	push rbp
	push r15
	push rbp
	mov rbp, rsp
	mov rcx, 0
print_loop:
	mov r11, [rbp+56]
	add r11, rcx
	movb r15b, [r11]
	inc rcx
	cmp r15b, 0x0#\0
	jnz print_loop#
print_loop_end:
	mov rax, 1
	mov rdi, 1
	mov rdx, rcx
	mov rsi, [rbp+56]
	syscall
	pop rbp
	pop r15
	pop rbp
	pop rcx
	pop r11
	pop rax
	ret

#exit
exit:
	mov rax, 60
	mov rdi, 1
	syscall

.data

_input:
	.asciz "00"

_jankenStartText:
	.asciz "JanKen~"

_descriptionText:
	.asciz "0:GU 1:CHOKI 2:PA\n"

_GU:
	.asciz "GU\n"
	
_CHOKI:
	.asciz "CHOKI\n"
	
_PA:
	.asciz "PA\n"

_lose:
	.asciz "YOU LOSE...bye!"

_win:
	.asciz "YOU WIN!!!bye!"

_draw:
	.asciz "DRAW!!WE ARE FRIENDS!!"

_exception:
	.asciz "FU*K YOU!!"

```
# syscallでの標準入力
標準入力したい！！いろいろあるだろうけど今日はsyscallでreadを使おう！！
#### syscallって？
syscallってのはOSが用意してくれているサブルーチンで
raxにsyscall番号を指定しその他レジスタに引数を入れ,`syscall`命令を実行することで簡単に呼び出すことができる

以下syscall一覧

https://filippo.io/linux-syscall-table/

#### readの使いかた
###### readのsyscall番号は０
なので`raxに0`を入れる
```
mov rax, 0
```
##### 引数
レジスタ|rdi|rsi|rdx|
----|----|----|----|
説明|ファイルディスクリプタ|バッファアドレス|文字数|


##### 標準入力をしたい場合
ファイルディスクリプタはstdinつまり`rdiに0`を入れればいい
```
mov rdi, 0
```
バッファはデータセグメント等に確保しておきそのアドレスを指定する

```
lea r11, _input
mov rsi,r11

_input:
    .ascii "  "
```
rdxには最大何文字まで入力させるかを指定する
```
mov rdx, 2
```

まとめると
```ruby:標準入力
mov rax, 0 #readのsyscall番号
mov rdi, 0 #stdin
lea r11, _input
mov rsi, r11 #buff adr
mov rdx, 2 #文字数
syscall

.data
_input:
    .ascii "  "
```

のようにすればよい

このプログラムではgetCharというルーチンを作っている．
読み込む文字数が2なのは改行を入れるため（そうしないとプログラムが終わった後shellに改行が行く．．
```ruby
#destroy rax
#input char to rax
getChar:
	push rcx
	push r11
	mov rax, 0#read
	mov rdi, 0#stdin
	lea r11, [_input]
	mov rsi, r11
	mov rdx, 2 
	syscall
	xor rax,rax
	movb al, _input
	pop r11
	pop rcx
	ret

```

# syscallでの標準出力
writeを使う

#### writeの使い方
##### syscall番号は1
なので`raxに1`を入れる
##### 引数
レジスタ|rdi|rsi|rdx|
----|----|----|----|
説明|ファイルディスクリプタ|文字列のアドレス|文字数|

##### 標準出力したい場合．
`rdiに1`（stdin）を入れる．
```ruby:標準出力
mov rax, 1 #writeのsyscall番号
mov rdi, 1　#stdout
lea r11, _text #string adr 
mov rsi,　r11 #文字数
syscall

_text:
    .ascii "hello unchi!"
```
<br>

このプログラムではprintというサブルーチンを作っている
文字列の長さをカウントしてくれるので文字列のアドレスをpushするだけで使える．
```ruby
#print string
#(string_adr)
print:
	push rax
	push r11
	push rcx
	push rbp
	push r15
	push rbp
	mov rbp, rsp
	mov rcx, 0
print_loop:
	mov r11, [rbp+56]
	add r11, rcx
	movb r15b, [r11]
	inc rcx
	cmp r15b, 0x0#\0
	jnz print_loop#
print_loop_end:
	mov rax, 1
	mov rdi, 1
	mov rdx, rcx
	mov rsi, [rbp+56]
	syscall
	pop rbp
	pop r15
	pop rbp
	pop rcx
	pop r11
	pop rax
	ret
```

# rdrand
rdrandとは・・・乱数を生成する回路により乱数を作り返す命令
最近実装された命令で2012年以降のintel製CPU，2015年以降のAMD製のCPUに実装されている．
なのでサポートしているかどうか判定するべきかもしれないが今回は省いている
（参考）

https://en.wikipedia.org/wiki/RDRAND

#### 使い方
```
rdrand rcx
```
のようにニーモニックの後に乱数を入れたいレジスタを指定するだけ．

このプログラムではgetRandというraxに3を法とした乱数を返すルーチンを作っている．
```ruby
getRand:
        push r11
        push rdx
        mov rax, 0
        mov rdx, 0
        rdrand rax
        mov r11,3
        div r11
        mov rax, rdx
        pop rdx
        pop r11
        ret
```

# 以上
コンパイルとリンクは
```
gcc -c janken.s -o janken.o&& ld -e main -o janken janken.o
```
でやる．


わかりにくかったら，許して．．

テストタヒね！！！課題くそ！！！
まあこんなもんでおわりです，うんこ！！

※バグあったけどめんどいからなおさない
追記：なおした

https://github.com/trimscash/asm_x86

# 参考

https://www.felixcloutier.com/x86/rdrand.html

https://filippo.io/linux-syscall-table/

https://en.wikipedia.org/wiki/RDRAND