Exercism 的 x86-64 軌道使用 NASM(The Netwide Assembler)作為組譯器。
在 NASM 中,註解以分號(;)開頭。
註解可以放在程式中的任何位置,而;之後的所有內容都會被組譯器忽略。
在 NASM 中,可以使用 equ 定義組譯時期的常數。
例如,以下程式碼定義了一個名為UNIVERSE、值為42的常數:
UNIVERSE equ 42
常數只會在定義時求值一次,之後就_不能_重新定義。
在組合語言中,我們通常使用暫存器來儲存值,而不是變數。 CPU 暫存器是處理器內部的一小塊快速記憶體。 大多數的運算都在 CPU 的暫存器中進行。
有些暫存器用於各種不同的運算,稱為通用暫存器(GPRs)。 其他的暫存器則有特殊或專門的用途。
在 x86-64 中,有 16 個 64 位元的通用暫存器(GPRs),它們也可以以 32 位元、16 位元或 8 位元的形式存取。
這些通用暫存器說明如下,其中rn裡的n範圍從 8 到 15:r8、r9、r10、r11、r12、r13、r14和r15。
| 64 位元 | 32 位元 | 16 位元 | 8 位元 |
|---|---|---|---|
| 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。
暫存器通常是使用push指令保留在堆疊上。
接著在函式結束前使用pop指令還原。
指令是 CPU 可以執行的一項運算。 它們通常具有以下形式:
name destination, source
也就是說,指令的名稱放在最前面,接著是至少一個空白,然後是目的運算元、一個逗號(,),最後是來源運算元。
指令通常不會修改來源運算元,只會修改目的運算元。
除非另有說明,否則兩個運算元的大小必須相同。 例如,如果來源運算元是 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 區段中。
函式的宣告由以下部分組成:
:。函式的引數如何傳入與回傳,有特定的慣例。 兩種主要的呼叫慣例是 System V AMD64 ABI 和 Microsoft x64。
這個軌道使用 System V AMD64 ABI 呼叫慣例,前六個整數引數會透過暫存器傳入函式。
它們依下列順序傳入: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