x86-64プロセッサでGNU assemblerを使う

# はじめに
x86-64プロセッサでAssembly Codeを作成する方法を説明します。

Assembly CodeをMachine Codeに変換するプログラムをAssemblerといいます。
ここではGNU assembler(GAS)を使用します。

Assembly Codeには決まり事があります。
この決まり事はApplication binary interface(ABI) or Calling conventionと呼ばれており、
instruction set / OS / compilerによって変わります。

x86-64プロセッサの代表的なABIは次の通りです。
それぞれUnix like OS / Windowsに対応します。

- System V AMD64 ABI
- Microsoft x64 calling convention

ここではSystem V AMD64 ABIについて説明します。

# cpu / os / gcc
使用したcpu / os / gccを明記します。

 - CPU Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
 - OS ubuntu-14.04-desktop-amd64
 - gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

# basic information
data type, register, instructionを説明します。

- data type
- general purpose register
- instructions

次の資料を参考にしました。
> [1] [System V Application Binary Interface AMD64 Architecture Processor Supplement Draft Version 0.3](https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf) 
> [2]Computer Systems - A Programmers Perspective
> [3]x86-64 Machine-Level Programming Randal E. Bryant David R. O’Hallaron September 9, 2005
> [4]Notes on x86-64 programming

# data type
x86-64 registers, memory and operationsは次に示すdata typesを使用します。
次の表はC declaration / Intel data type / GAS suffix / x86-64 Sizeの対応表です。

| C declaration | Intel data type | GAS suffix | x86-64 Size (Bytes) |
|:--|:--|:-:|:-:|
|char|Byte|b|1|
|short|Word|w|2|
|int|Double word|l|4|
|unsigned|Double word|l|4|
|long int|Quad word|q|8|
|unsigned long|Quad word|q|8|
|char *|Quad word|q|8|
|float|Single precision|s|4|
|double|Double precision|d|8|
|long double|Extended precision|t|16|

Long / Pointerは64bitです。LP64として知られています。
なお、Microsoft x64 calling convention は
Long Long / Pointerが64bitです。LLP64として知られています。

GAS suffixとはGNU assemblerのニーモニックの後ろに付く文字です。
mov命令を例にすると、
movbはオペランドが1byteであることを、
movqはオペランドが8byteであることを示します。

なお、GAS suffixは省略できます。具体的には
**movl** %eax, %ebx と記載しなくても、単に**mov** %eax, %ebxでよいです。

# general purpose register
16個のgeneral purpose registers(GPRs)が利用できます。サイズは64bit長です。

```
63                        31            15      7      0 bit
 -------------------------------------------------------
 | rax                     |  eax     ax |  ah  |   al |
 -------------------------------------------------------
 | rbx                     |  ebx     bx |  bh  |   bl |
 -------------------------------------------------------
 | rcx                     |  ecx     cx |  ch  |   cl |
 -------------------------------------------------------
 | rdx                     |  edx     dx |  dh  |   dl |
 -------------------------------------------------------
 | rsi                     |  esi     si |      |  sil |
 -------------------------------------------------------
 | rdi                     |  edi     di |      |  dil |
 -------------------------------------------------------
 | rbp                     |  ebp     bp |      |  bpl |
 -------------------------------------------------------
 | rsp                     |  esp     sp |      |  spl |
 -------------------------------------------------------
 |  r8                     |  r8d    r8w |      |  r8b |
 -------------------------------------------------------
 |  r9                     |  r9d    r9w |      |  r9b |
 -------------------------------------------------------
 | r10                     | r10d   r10w |      | r10b |
 -------------------------------------------------------
 | r11                     | r11d   r11w |      | r11b |
 -------------------------------------------------------
 | r12                     | r12d   r12w |      | r12b |
 -------------------------------------------------------
 | r13                     | r13d   r13w |      | r13b |
 -------------------------------------------------------
 | r14                     | r14d   r14w |      | r14b |
 -------------------------------------------------------
 | r15                     | r15d   r15w |      | r15b |
 -------------------------------------------------------
```

同じregisterを32bit / 16bit / 8bit長でアクセスできます。
raxを例に説明します。名称と対応するbitは以下の通りです。
raxは64bit
eaxは下位32bit
axは下位16bit
ahは下位16bitの上位8bit
alは下位16bitの下位8bit

各registerは次に示すように用途が決められています。

 - Return value : rax
 - Argument : rdi(1st) rsi(2nd) rdx(3rd) rcx(4th) r8(5th) r9(6th)
 - Callee saved : rbx rbp r13-15
 - Stack pointer : rsp

(*)資料[1]には
r10はtemporary register, used for passing a function’s static chain pointer
r11はtemporary register
r12はcallee-saved register
と記載されています。

Callee saved registerは呼出し先で変更できないregisterです。
呼出し元の視点ではcall命令の前後で値が変わらないことが保証されます。

呼出し先でCallee saved registerを利用する場合は
registerの値をmemoryに保存・復元する必要があります。

次のregisterは関数の引数に利用されます。

| Arg # | Size (bits) | > | > | > |
|:-:|:-:|:-:|:-:|:-:|
||64|32|16|8|
|1|%rdi|%edi|%di|%dil|
|2|%rsi|%esi|%si|%sil|
|3|%rdx|%edx|%dx|%dl|
|4|%rcx|%ecx|%cx|%cl|
|5|%r8|%r8d|%r8w|%r8b|
|6|%r9|%r9d|%r9w|%r9b|

必要に応じてcall命令の前にregisterに値を設定します。

例えばadd(int a, int b)という関数をadd(10, 20)のように呼び出す場合、
次のAssembly Codeになります。%esi(1st)に10を%edi(2nd)に20を設定してcallします。

```gas:test.s
    .global main
format:
    .asciz "%d\n"
main:
    mov $10, %esi
    mov $20, %edi
    call add
    mov $format, %rdi
    mov %eax, %esi
    xor %rax, %rax
    call printf
    ret
add:
    xor %rax, %rax
    add %edi, %eax
    add %esi, %eax
    ret
```

# instructions
x86-64 Instruction setを説明します。
以降の説明で用いる用語を説明します。

- Iは直値です。
- Rはregisterです。
- Sは直値 or register or memoryです。
- Dはregister or memoryです。
- Mはmemoryです。
- (*1)のInstructionには後ろに[b|w|l|q]が付きます。
- (*2)のInstructionには後ろに[bw|bl|bq|wl|wq|lq]が付きます。

(*1)の例としてmov(*1)の場合、
movb / movw / movl / movqとなります。b,w,l,qはGAS suffixです。

(*2)の例としてmovs(*2)の場合、
movsbw / movsbl / movsbq / movswl / movswq / movslqとなります。
bwの意味は b → wの変換をするという意味です。bl/bq/wl/wq/lqも同様の意味です。

## Data movement
register / memory間でデータをコピーする命令を説明します。
Instructionの一覧を示します。

| Instruction | Effect | Description |
|:--|:--|:--|
|mov(*1) S, D|S → D|Move|
|movabsq I, R|I → R|Move quad word|
|movs(*2) S, R|SignExtend(S) → R|Move sign-extended|
|movz(*2) S, R|ZeroExtend(S) → R|Move zero-extended|
|pushq S|R[%rsp]-8 → R[%rsp];<br>S → M[R[%rsp]]|Push|
|popq D|M[R[%rsp]] → D;<br>R[%rsp]+8 → R[%rsp]|Pop|

movは同一サイズのデータをコピーする命令です。DにSをコピーします。
movabsqは64bitの直値を64bit registerにコピーします。
movsはsign extension, movzはzero extensionします。詳細は後で説明します。
pushq / popqはstackに8byteデータをpush / popします。

(*)movzlq命令はありません。理由は[3]に次のように記載されています。

> Perhaps unexpectedly, instructions that move or generate 
> 32-bit register values also set the upper 32 bits of the register to zero.
> Consequently there is no need for an instruction movzlq.

## sign extension / zero extension
データをサイズの大きい領域にコピーする場合、次の2つの方式があります。

- sign extension
- zero extension

sign extensionは

 - コピー元の最上位ビットが1であればコピー先の上位ビットに1を埋める、
 - コピー元の最上位ビットが0であればコピー先の上位ビットに0を埋める、

方式です。

zero extensionはコピー先の上位ビットに0を埋める方式です。

sign extensionでは符号が変化しません。
zero extensionでは符号が変化します。
扱う整数が符号付きかどうかで使い分けます。

例.1byte を 2byteに代入するケース

```
case 1: 0010 1101 (signed +0x2d, unsigned 0x2d)
 0010 1101 b -> 0000 0000 0010 1101 b (sign ext; +0x002d)
 0010 1101 b -> 0000 0000 0010 1101 b (zero ext;  0x002d)

case 2: 1010 1101 (signed -0x53, unsigned 0xad)
 1010 1101 b -> 1111 1111 1010 1101 b (sign ext; -0x0053)
 1010 1101 b -> 0000 0000 1010 1101 b (zero ext;  0x00ad)
```

## Addressing Modes
mov命令はmemoryを参照できます。memory addressは次の形式で参照します。
effective addressといいます。

> offset(base,index,scale)

effective addressは(base + index * scale + offset)で計算します。

offsetは固定値 or labelです。base / indexはregisterです。
scaleは1 or 2 or 4 or 8です。

例. %rsp

```gas
# %rspに設定されたaddressにあるwordを%diにコピーします。
# offset / index / scaleは省略されています。
mov (%rsp), %di

# mov %rsp, %di
# ()なしにすると%rspの内容をコピーすることになります。誤りです。
```

## PC-relative addressing
x86-64でコンパイルした実行ファイルをobjdumpでdisassembleすると
%ripからの相対アドレスで表現している命令が多くあることに気づきます。
%ripを使ったAddressingをPC-relative addressingといいます。

例. 

```
$ objdump -d test | grep rip
  4003e4:	48 8b 05 0d 0c 20 00 	mov    0x200c0d(%rip),%rax        # 600ff8 <_DYNAMIC+0x1d0>
  400400:	ff 35 02 0c 20 00    	pushq  0x200c02(%rip)        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
(...)
$ 
```

この形式が使われる理由は次の通りです。

> Offsets are limited to 32 bits. This means that only a 4GB window into the
> potential 64-bit address space can be accessed from a given base value. This is
> mainly an issue when accessing static global data. It is standard to access this
> data using PC-relative addressing (using %rip as the base). For example,
> we would write the address of a global value stored at location labeled a as
>  a(%rip), meaning that the assembler and linker should cooperate to compute the
>  offset of a from the ultimate location of the current instruction.

## Integer arithmetic & logic operations
Integerのarithmetic & logic operationsを説明します。

- lea
- arithmetic : inc / dec / add / sub
- logic : neg / not / xor / or / and
- shift : sal / sar / shl / shr

次のInstructionはすべて(*1)となります。

| Instruction | Effect | Description |
|:--|:--|:--|
|lea S, D|&S → D|Load effective address|
|inc D|D+1 → D|Increment|
|dec D|D-1 → D|Decrement|
|add S, D|D+S → D|Add|
|sub S, D|D-S → D|Subtract|
|neg D|-D → D|Negate|
|not D|~D →D |Complement|
|xor S, D|D^S → D|Exclusive-or|
|or S, D|D&#124;S → D|Or|
|and S, D|D&S → D|And|
|sal k, D|D<<k → D|Left shift|
|shl k, D|D<<k → D|Left shift (same as sal)|
|sar k, D|D>>k → D|Arithmetic right shift|
|shr k, D|D>>k → D|Logical right shift|

lea / sarについて補足説明します。
lea はeffective addressをDに設定します。

例. lea と movの違い
movは%ripが指し示すaddressの先の8byteを%rsiにコピーします。
leaは%ripが指し示すaddressを%rsiにコピーします。

```gas
lea (%rip), %rsi # 0x400542
mov (%rip), %rsi # 0xfffffebfe8c03148
```

sarは最上位ビットを保持したまま右bit shiftします。
つまり
最上位ビットが1であればシフトしてできる左側には1を詰めます。
最上位ビットが0であればシフトしてできる左側には0を詰めます。

例. sar / shrの違い

```gas
mov $0x80, %sil
sar $7, %sil # %sil = 0xFF
mov $0x80, %sil
shr $7, %sil # %sil = 0x01
```

## Multiplication and division operations

|Instruction|Effect|Description|
|:--|:--|:--|
|imulq S|S × R[%rax]→R[%rdx]:R[%rax]|Signed full multiply|
|mulq S|S × R[%rax]→R[%rdx]:R[%rax]|Unsigned full multiply|
|cltq|SignExtend(R[%eax])→R[%rax]|Convert %eax to quad word|
|cqto|SignExtend(R[%rax])→R[%rdx]:R[%rax]|Convert to oct word|
|idivq S|R[%rdx]:R[%rax] mod S→R[%rdx]<br>R[%rdx]:R[%rax]÷S→R[%rax]|Signed divide|
|divq S|R[%rdx]:R[%rax] mod S→R[%rdx]<br>R[%rdx]:R[%rax]÷S→R[%rax]|Unsigned　divide|

- sample

```gas:test.s
    .global main
format:
    .asciz "%016lx %016lx\n"
main:
    movabs $0xFFFFFFFFFFFFFFFF, %rax
    movabs $0xFFFFFFFFFFFFFFFF, %r10
    mul %r10 # %rdx:%rax
    mov $format, %rdi
    mov %rdx, %rsi
    mov %rax, %rdx
    xor %rax, %rax
    call printf
    ret
```

## FLAGS register
CPUは直前に実行したinstructionに応じて状態をFLAGS registerに保持します。
FLAGS registerを使って処理を分岐させます。

pushf / popf instructionでFLAGS registerをstackにpush / popできます。
pushした値をmovでreadすることで FLAGS registerを読み込めます。

FLAGS registerのbit assignは次の通りです。

```
  15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0 
 -------------------------------------------------
 |  |  |  |  |OF|DF|IF|TF|SF|ZF| 0|AF| 0|PF| 1|CF|
 -------------------------------------------------

CF:Carry Flag
PF:Parity Flag
AF:Adjust Flag
ZF:Zero Flag
SF:Sign Flag
TF:Trap Flag
IF:Interrupt Enable Flag
DF:Direction Flag
OF:Overflow Flag
```

read方法は次の通りです。

```gas
    .global main
format:
    .asciz "0x%04x\n"
main:
    xor %rax, %rax
    pushf
    mov (%rsp), %ax
    popf
    mov %eax, %esi
    mov $format, %rdi
    xor %rax, %rax
    call printf
    ret
```

FLAGS registerをprintします。

```gas:test.s
    .global main
format:
    .asciz "ZF=%01x SF=%01x CF=%01x OF=%01x\n"
print_flags:
    xor %r13, %r13
    mov %di, %r13w
    mov $format, %rdi # 1st
    xor %rsi, %rsi    # 2nd ZF
    bt  $6, %r13w
    lahf
    mov %ah, %al
    mov %al, %sil
    and $0x01, %sil
    xor %rdx, %rdx    # 3rd SF
    bt  $7, %r13w
    lahf
    mov %ah, %al
    mov %al, %dl
    and $0x01, %dl
    xor %rcx, %rcx    # 4th CF
    mov %r13b, %cxl
    and $0x01, %cxl
    xor %r8,  %r8     # 5th OF
    bt  $11, %r13w
    lahf
    mov %ah, %al
    mov %al, %r8b
    and $0x01, %r8b
    xor %rax, %rax
    call printf
    ret
main:
    xor %rdi, %rdi  # %rdi=0
    mov $0x7f, %bl
    add $0x00, %bl
    pushf
    mov (%rsp), %di # %di=FLAGS register
    popf
    call print_flags
    ret
```

### ZF:Zero Flag
0であれば1、そうでなければ0になります。

例.

```gas
mov $0x00 %bl
test %bl, %bl # ZF = 1
mov $0x01 %bl
test %bl, %bl # ZF = 0
```

### SF:Sign Flag
負であれば1、そうでなければ0になります。
結果の最上位ビットを返します。負であれば最上位ビットは1です。

例.

```gas
mov $0x80 %bl
test %bl, %bl # SF = 1
mov $0x7f %bl
test %bl, %bl # SF = 0
mov $0x00 %bl
test %bl, %bl # SF = 0
```

### CF:Carry Flag
符号なし演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号なし 8bit range(0~255)

```gas
mov $0xff %bl
add $0x01 %bl # CF = 1, 255+1=256
mov $0xfe %bl
add $0x01 %bl # CF = 0, 254+1=255
mov $0x01 %bl
sub $0x01 %bl # CF = 0, 1-1=0
mov $0x01 %bl
sub $0x02 %bl # CF = 1, 1-2=-1
```

### OF:Overflow Flag
符号付き演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号付き 8bit range(-128~0~127), 0xff(-1)~0x80(-128)~0x7f(127)~0x00(0)

```gas
mov $0x7f %bl
add $0x01 %bl # OF = 1, 127+1=128
mov $0x7e %bl
add $0x01 %bl # OF = 0, 126+1=127
mov $0x80 %bl
sub $0x01 %bl # OF = 1, -128-1=-129
mov $0x81 %bl
sub $0x01 %bl # OF = 0, -127-1=-128
```

## Condition Codes
FLAGS registerの組み合わせに応じてCondition Codes(cc)が決まります。
Condition Codesに対応したjump命令があります。

cmp / test命令を利用するとregisterを変更することなしにFLAGS registerを更新できます。

- cmp s2,s1  : set flags based on s1 - s2
- test s2,s1 : set flags based on s1&s2 (logical and)

cmp実行後のFLAGS registerとccの対応を次に示します。
ccに応じてjump命令が決まります。
フォーマットはjccとなります。例. cc=eであればjeとなります。

| cc | condition tested | meaning after cmp |
|:-:|:--|:--|
|e|ZF|equal to zero|
|ne|˜ ZF|not equal to zero|
|s|SF|negative|
|ns|˜ SF|non-negative|
|g|˜ (SF xor OF) & ˜ ZF|greater (< signed)|
|ge|˜ (SF xor OF)|greater or equal (<= signed)|
|l|SF xor OF|less (signed <)|
|le|(SF xor OF) &#124; ZF|less or equal (signed <=)|
|a|˜ CF & ˜ ZF|above (< unsigned)|
|ae|˜ CF|above or equal (<= unsigned)|
|b|CF|below (< unsigned)|
|be|CF &#124; ZF|below or equal (<= unsigned)|

# sample code
## printf
レジスタの値をprintfするだけのsimpleなAssembly Codeを示します。
esiをprintfします。

```gas:test.s
    .global main
main:
    mov  $format, %rdi # 1st parameter
    mov  $10,  %rsi    # 2nd parameter
    xor  %rax, %rax    #
    call printf        # printf("%d\n", %rsi)
    ret
format:
    .asciz "%d\n"
```

```bash:
gcc -O2 test.s -o test && ./test
10
```

## rdtscp
rdtscp命令は次の2つの情報を読み出すことができます。

 - Time-Stamp Counter
 - Processor ID

命令実行後に次のレジスタが更新されます。

 - edx:eax : 64bit Time-Stamp Counter
 - ecx : Processor ID

Time-Stamp CounterはCPUのclockに合わせてインクリメントされるカウンタ値です。

sample codeを示します。rdtscpを連続して実行します。
1度目のrdtscp後にEAXを退避してすぐに2度目のrdtscpを実行します。
1度目と2度目の差分のカウンタ値をprintfします。

ばらつきがありますが最小で77 clockかかりました。

```gas:test.s
    .global main
main:
    movl $10, %r13d
loop:
    rdtscp
    mov  %eax, %r8d
    rdtscp
    mov  %eax, %r9d
    sub  %r8d, %r9d
    mov  $format, %rdi # 1st parameter
    mov  %r9d,  %esi   # 2nd parameter
    mov  %ecx,  %edx   # 3rd parameter
    xor  %rax, %rax    #-
    call printf        # printf
    decl %r13d
    jne  loop
    ret
format:
    .asciz "%03d %d\n"
```

```bash:console
$ ./test
088 1
077 1
088 1
099 1
088 1
...
```