基礎

基礎 在 x86-64 Assembly

1 個練習

關於 基礎

註解

Exercism 的 x86-64 軌道使用 NASM(The Netwide Assembler)作為組譯器。 在 NASM 中,註解以分號(;)開頭。

註解可以放在程式中的任何位置,而;之後的所有內容都會被組譯器忽略。

常數

在 NASM 中,可以使用 equ 定義組譯時期的常數。 例如,以下程式碼定義了一個名為UNIVERSE、值為42的常數:

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 位元 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。

Note

暫存器通常是使用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 區段中。

函式的宣告由以下部分組成:

  1. 一個帶有函式名稱的標籤,後面接著:。
  2. 定義該函式的指令。
  3. 回傳指令 ret。

函式的引數如何傳入與回傳,有特定的慣例。 兩種主要的呼叫慣例是 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
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 基礎