基本

基本 の x86-64 Assembly

1個の演習

基本について

コメント

Exercismのx86-64トラックでは、アセンブラーとしてNASM(The Netwide Assembler)を使います。 NASMでは、コメントの先頭にセミコロン(;)を付けます。

コメントはプログラムのどこにでも書くことができ、;より後ろはアセンブラーによって無視されます。

定数

NASMでは、equを使ってアセンブル時の定数を定義できます。 たとえば、次のコードは42という値を持つUNIVERSEという名前の定数を定義します:

UNIVERSE equ 42

定数は定義されたときに一度だけ評価され、後から再定義することは_できません_。

汎用レジスタ(GPRs)

アセンブリでは、変数の代わりに、値を格納するためにレジスタをよく使います。 CPUレジスタは、コンピューターのプロセッサー内部にある高速なメモリーです。 ほとんどの計算は、CPUのレジスタで行われます。

一部のレジスタはさまざまな計算に使われ、**汎用レジスタ(GPRs)**と呼ばれます。 それ以外のレジスタには、特別な目的や専用の目的があります。

x86-64には16個の64ビット汎用レジスタ(GPRs)があり、32ビット、16ビット、8ビットとしてもアクセスできます。 汎用レジスタを以下に示します。ここでrnのnは8から15の範囲です: r8、r9、r10、r11、r12、r13、r14、r15。

64-bit 32-bit 16-bit 8-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
rn rnd rnw rnb

64ビット未満を使う場合、アクセスされるビットは通常、レジスタの下位部分です。 この規則の例外はah、bh、ch、dhで、これらはレジスタの16ビット部分の上位8ビットにアクセスします。

raxレジスタでビットにアクセスする方法の図:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

これらのレジスタのいくつかは、関数呼び出しをまたいで保存しておかなければなりません: rbp、rsp、rbx、r12、r13、r14、r15。 これらを保存しないと、エラーや未定義の動作につながることがあります。

それ以外のレジスタは保存されないため、自由に使えます: rax、rcx、rdx、rdi、rsi、r8、r9、r10、r11。

Note

レジスタは通常、push命令を使ってスタックに保存します。 そして関数の終わりに、pop命令で元に戻します。

命令

命令は、CPUが実行できる計算の単位です。 命令は通常、次のような形をしています:

name destination, source

つまり、最初に命令の名前を書き、次に1つ以上の空白、続いて宛先オペランド、コンマ(,)、そして最後にソースオペランドを書きます。 通常、命令によって変更されるのは宛先オペランドだけで、ソースオペランドは変更されません。

特に断りのない限り、両方のオペランドは同じサイズでなければなりません。 たとえば、ソースオペランドが16ビットなら、宛先オペランドも16ビットでなければなりません。

たとえば、レジスタに値を格納するには、mov命令を使います:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

上のコードは、ソースオペランドであるrdxの64ビットすべての内容を、この命令の宛先オペランドであるraxの64ビットすべてにコピーします。

ほとんどの命令では、ソースオペランドとして整数の定数を使えます(宛先オペランドとしては使えません):

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

32ビットレジスタに書き込むと上位ビットもクリアされるため、mov eax, 42はmov rax, 42と同じであることに注意してください。 これは16ビットレジスタと8ビットレジスタには当てはまりません。

加算、減算、乗算の算術演算には、add、sub、imul命令を使います:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

関数

命令は関数の中にまとめます。 すべての関数は、ソースファイルのsection .textに置きます。

関数の宣言は、次の要素で構成されます:

  1. 関数名を付けたラベルと、それに続く:。
  2. 関数を定義する命令。
  3. 戻り命令であるret。

関数に引数を渡す方法と、関数から値を返す方法には、特定の取り決めがあります。 主な呼び出し規約には、System V AMD64 ABIとMicrosoft x64の2つがあります。

このトラックではSystem V AMD64 ABI呼び出し規約を使い、最初の6つの整数引数はレジスタで関数に渡されます。 引数は次の順序で渡されます: rdi、rsi、rdx、rcx、r8、r9。

整数の戻り値は、raxレジスタで関数から返されます。

関数を呼び出すには、call命令を使います。 関数は、同じソースファイル内のどこからでも呼び出せます。 関数を他のソースファイルから見えるようにするには、globalディレクティブを使う必要があります。

たとえば、次のコードはsumという関数を宣言します:

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

そして次のコードは、引数3と5でsum関数を呼び出します:

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

基本を学習する