---
# System prepended metadata

title: Assembly Language / Ch02 - Assembly Language Fundamentals
tags: [Assembly Language]

---

# Assembly Language / Ch02 - Assembly Language Fundamentals

## 0. 前言

之所以被稱為「組合語言」，是因為它與硬體密切相關，直接「組合」出機器指令。組合語言的每個指令幾乎一一對應機器語言中的指令，這使得組合語言能夠非常高效地控制硬體資源如記憶體、處理器等，成為計算機體系結構和優化程式性能的重要工具。

「組合」一詞源自於它對高級語言的抽象程度較低，開發者需要「組合」機器的不同部件和資源來執行任務。每條組合語言的指令通常與一個或多個機器碼指令對應，因此組合語言被視為介於機器碼與高級程式語言之間的低階語言。

以北市大資科系 - 組合語言課程為例，我們將使用 Microsoft Macro Assembler (MASM) 和 Irvine32 庫來學習 x86 組合語言的基礎知識。

## 1. Basic Elements

### 1.1 整數常量（Integer Constants）

整數常量在組合語言中可以用多種方式表示：

- 可選的前導 + 或 - 符號
- 二進制、十進制、八進制或十六進制數字
- 常見的基數字符：
  - h - 十六進制
  - d - 十進制
  - b - 二進制
  - r - 編碼實數

例如：
```assembly
mov eax, 30d    ; 十進制 30
mov ebx, 6Ah    ; 十六進制 6A (即十進制的 106)
mov ecx, 1101b  ; 二進制 1101 (即十進制的 13)
```

> *注意：以字母開頭的十六進制數需要加 0 前綴，如 `0A5h`。*

### 1.2 字符和字符串常量（Character and String Constants）

字符常量用單引號或雙引號括起來，如 `'A'` 或 `"x"`。ASCII 字符佔用 1 個字節。

字符串常量同樣用引號括起，每個字符佔用一個字節：

```assembly!
message BYTE "Hello, World!", 0
```
上面的程式碼定義一個字串 "Hello, World!"，並以 NULL 字元（0）結尾，很像 C 啦。

### 1.3 保留字和標識符（Reserved Words and Identifiers）

#### 1.3.1 保留字

ㄟ，組合語言不像 C 或 Python 等高級程式語言那樣擁有「保留字」的概念。相反，它們由特定於處理器架構的指令、指令碼、暫存器或偽指令組成。以下是組合語言中常見的各類關鍵字或保留名稱，而這些可能根據不同的處理器架構（例如 x86、ARM、MIPS）而有所不同。

1. **指令（Instructions）**
- x86： `MOV`, `ADD`, `SUB`, `CMP`, `JMP`, `PUSH`, `POP`
- ARM： `LDR`, `STR`, `ADD`, `SUB`, `MOV`, `B`（分支）
- MIPS： `LW`（載入字）, `SW`（儲存字）, `ADD`, `SUB`, `J`（跳轉）

*（這邊先看過就好等等會介紹）*

2. **指令碼（Directives）**
指令碼是用來告訴組譯器如何處理代碼的指令，這些指令可以用來定義記憶體、設置常數或定義程式的區段。常見的指令碼包括：
- **`.data`** – 定義數據區段
- **`.text`** – 定義程式（文字）區段
- **`.global`** – 指定全域符號
- **`.byte`**, **`.word`**, **`.long`** – 資料分配指令碼
- **`.align`** – 在記憶體中對齊資料

*（這邊先看過就好等等會介紹）*

3. **暫存器**
- x86： `EAX`, `EBX`, `ECX`, `EDX`, `ESP`, `EBP` 等
- ARM： `R0`, `R1`, `R2`... `R15`, `SP`（堆疊指標）, `LR`（連結暫存器）, `PC`（程式計數器）

4. **偽指令**
偽指令並不是實際的機器指令，但由組譯器提供以便於編寫程式。
- x86：`NOP`（無操作）, `LEA`（載入有效地址）
- ARM：`NOP`, `MOVT`（載入高半字）

#### 1.3.2 標識符的命名規則

在 MASM 中，標識符是用來表示記憶體位址、常數、標籤、宏、結構、數據和其他程式元素的名稱。它用來標記特定位置，以便在指令中引用和方便開發者閱讀程式。

1. **允許的字元**：
  - 標識符可以包含字母（A-Z, a-z）、數字（0-9）和特殊符號（_）。
  - 標識符長度最多可達 247 個字元，前 31 個字元通常是有效的。
  - 第一個字符必須是字母、_、@、? 或 $，，但不能以數字開頭。
   
2. **大小寫不敏感**：
  - MASM 中，標識符 **不區分大小寫**，因此 `myLabel` 和 `MYLABEL` 在 MASM 中被視為相同的標識符。

3. **不能與 MASM 指令或關鍵字衝突**：
  - 標識符不能與 MASM 的內建指令、關鍵字（如 `MOV`、`ADD`、`DWORD` 等）同名。這些是組譯器的保留字。

4. **合法範圍**：
  - 標識符的命名範圍包括變數、常數、宏名稱、標籤、結構體名稱、組件名稱等。

### 1.4 Directives（指令碼）

---

:::spoiler **貼心提醒**

#### 細心的好朋朋應該會發現，這裡標題英文跟中文的順序反過來ㄌ，因為在這裡英文定義的重要程度之高，無法讓我用比較好理解的中文解釋，準備好就出發哩。

:::

---

Directives 用來指示組譯器如何處理程式碼。它們不會轉換成機器碼、不直接影響程式的執行，而是控制組譯器的行為，如分配記憶體、設定段落、定義數據、控制組譯過程等。更多的是，它不是 Intel 指令集的一部分、大小寫不敏感、NASM 的 Directives 也不與 MASM 相同。

#### 1.4.1 MASM 中常見的 Directives
- **`.data`**：用來定義數據段，通常用來宣告變數或常數。
  ```assembly
  .data
  myVar DWORD 1234h  ; 定義 32 位元變數 myVar，值為 1234h
  ```
- **`.code`**：用來定義程式碼段，這裡寫入的指令會被執行。
  ```assembly
  .code
  main PROC          ; 定義主程式開始
  ```
- **`.stack`**：用來定義堆疊段，通常用來分配堆疊空間。
  ```assembly
  .stack 100h        ; 分配 256 字節的堆疊空間
  ```
- **`EQU`**：用來定義常數。
  ```assembly
  MAX_LENGTH EQU 256 ; 定義 MAX_LENGTH 常數，值為 256
  ```
- **`PROC`** 和 **`ENDP`**：用來定義過程（即函數）。
  ```assembly
  main PROC          ; 定義名為 main 的過程
  main ENDP          ; 結束過程定義
  ```
- **`SEGMENT`** 和 **`ENDS`**：用來定義段。段是程式中用來區分代碼和數據的區域。
  ```assembly
  x_segment SEGMENT
  message db 'Hello, world!', 0
  x_segment ENDS
  ```
  
---
  
**Directives 可以做到：**

- **記憶體分配**：在組譯過程中分配記憶體，如 `.data` 段用來分配變數的存儲空間。
- **段落定義**：程式的不同段落得以被區分，例如程式碼段 `.code` 和數據段 `.data`。
- **控制組譯器行為**：告訴組譯器如何處理代碼，而不會直接生成機器指令。例如，`EQU` 定義常數，但它只在組譯階段存在，不會影響最終的可執行程式。

### 1.5 Instructions（指令）

Instructions 是直接由 CPU 執行的命令，它們會被翻譯成二進制機器碼，並用來操作 CPU 的暫存器、記憶體單元，進行數學運算、邏輯操作、資料傳輸、流程控制等。

**Instructions 包含這四個東東：**

:::success
##### Label 標籤（非必要）
:::
:::danger
##### Instruction Mnemonic 助記符（aka指令本人，必要）
:::
:::warning
##### Operand 操作數 （倚賴 Instructions 決定）
:::
:::success
##### Comment 註釋（非必要）
:::


Mnemonic（助記符）是指令的名稱，描述指令的功能，例如 MOV, ADD, JMP 等。Operand（操作數）是指令作用的對象，可以是暫存器、常數、記憶體地址等，決定了操作的具體數據。助記符告訴 CPU 要執行什麼操作，操作數則告訴 CPU 要對哪個數據進行操作。

*（Label 跟 Comment 先看過就好等等會介紹）*

**一行合格的 Instructions Be Like：**

:::info
```assembly
[label:] mnemonic operand(s)[;comment]
```
```assembly
[標籤:] 助記符 操作數 [;註釋]
```
:::

例如：
```assembly
start: mov eax, 5   ; 將 5 移動到 EAX 暫存器
```

#### 1.5.1 MASM 中常見的 Instructions
- **資料傳輸指令**：
  - `MOV`：將資料從一個位置移動到另一個位置。
    ```assembly
    MOV AX, 5           ; 將數值 5 傳送到 AX 暫存器
    MOV BX, AX          ; 將 AX 的值傳送到 BX 暫存器
    ```
  - `PUSH` / `POP`：將資料壓入或彈出堆疊。
    ```assembly
    PUSH AX             ; 將 AX 暫存器的值壓入堆疊
    POP BX              ; 從堆疊彈出數據至 BX 暫存器
    ```

- **算術指令**：
  - `ADD`：相加兩個數值。
    ```assembly
    ADD AX, BX          ; 將 BX 的值加到 AX 中
    ```
  - `SUB`：相減兩個數值。
    ```assembly
    SUB AX, 1           ; AX 減去 1
    ```

- **邏輯指令**：
  - `AND`、`OR`、`XOR`：進行位元邏輯運算。
    ```assembly
    AND AX, 0FFh        ; 將 AX 與 0FFh 進行 AND 運算
    ```
  - `NOT`：對暫存器中的位元取反。
    ```assembly
    NOT AX              ; 對 AX 取反
    ```

- **流程控制指令**：
  - `JMP`：無條件跳轉到程式的某一位置。
    ```assembly
    JMP start           ; 無條件跳轉到標籤 start
    ```
  - `CALL` / `RET`：呼叫過程並返回。
    ```assembly
    CALL myFunc         ; 呼叫名為 myFunc 的過程
    RET                 ; 從過程返回
    ```

- **中斷指令**：
  - `INT`：呼叫中斷服務例程（ISR）。
    ```assembly
    INT 21h             ; 呼叫 DOS 中斷，進行系統操作（如輸出字串）
    ```

---
  
**Instructions 可以做到：**

- **資料操作**：Instructions 用來移動資料、進行運算或執行邏輯運算。
- **流程控制**：Instructions 能改變程式的執行流程，通過條件或無條件的跳轉來控制程式的執行順序。
- **中斷和系統呼叫**：使用 `INT`指令可以呼叫作業系統的服務或硬體中斷來完成某些功能，例如輸出訊息、結束程式等。

### 1.6 **Directives vs. Instructions**
- **Directives（指令碼）**：只在組譯階段發揮作用，幫助組譯器理解如何編譯程式，不會轉換成實際的機器指令。例如 `.data`, `.code`, `EQU` 這些指令碼不會影響 CPU 執行。
  
- **Instructions（指令）**：會被組譯器翻譯成機器碼，並由 CPU 執行，直接影響程式的執行行為。指令如 `MOV`, `ADD`, `JMP` 等等，這些指令最終會生成 CPU 可以解釋和執行的機器碼。

**Overrall, Instructions is for the assembler, and Directives is for CPU.**  
學廢了嗎 (⁎⁍̴̛ᴗ⁍̴̛⁎)

### 1.7 標籤（Labels）

標籤（Label）是用來**標識程式中的特定位置**或**記憶體地址**，方便程式在執行過程中進行跳轉、迴圈、條件分支等控制流操作。主要功能是為某些位置提供一個可辨識的名稱，以便後續指令可以引用這些位置，而無需直接使用難以理解的記憶體地址或數值。

#### 1.7.1 標籤的功能
標籤的主要作用是提供一個位置參考點，在程式中常用於以下幾個場景：
1. **流程控制**：用於跳轉指令（如 `JMP`）或條件分支（如 `JE`, `JNE`）來指示程式執行時應跳轉的目標位置。
2. **迴圈控制**：在迴圈中，標籤可以標識迴圈的開始或結束位置，用於實現重複執行的邏輯。
3. **函數呼叫**：當呼叫函數時，標籤用來標識函數的入口，並在執行完函數後返回。
4. **中斷或例外處理**：可以用標籤標識例外處理程式的位置。

#### 1.7.2 標籤的命名規則
- 不能以數字開頭。
- 大小寫不敏感。
- 在 MASM 中，標籤通常以冒號 `:` 結尾來標識其為標籤。
- 例如：`Start`, `Loop1`, `EndFunc` 等。

#### 1.7.3 標籤的使用範例

- **無條件跳轉**
    - `JMP`：無條件跳轉到程式的某一位置。
        ```assembly
        start:                ; 定義標籤 start
            MOV AX, 5         ; 將數值 5 輸入 AX 暫存器
            JMP end           ; 無條件跳轉到標籤 end

        middle:
            ADD AX, 1         ; AX 的值加 1 (這行不會執行到)
    
        end:                  ; 定義標籤 end
            MOV BX, AX        ; 將 AX 的值移動到 BX
            INT 20h           ; 結束程式
        ```
    - 標籤 `start` 和 `end` 被用來標識程式的特定位置。
    - 當執行到 `JMP end` 指令時，程式會跳過標籤 `middle` 處的指令，直接跳轉到標籤 `end` 的位置繼續執行。

- **條件跳轉**
    - `JE`, `JNE`, `JG`：當特定條件滿足時，程式會跳轉到相應的標籤。
        ```assembly
        CMP AX, BX        ; 比較 AX 和 BX
        JE equal          ; 如果相等，跳轉到標籤 equal

        MOV CX, 1         ; 如果不相等，執行這行指令
        JMP end           ; 然後跳轉到結束

        equal:                ; 定義標籤 equal
            MOV CX, 0         ; 如果相等，將 0 存入 CX

        end:                  ; 定義標籤 end
            INT 20h           ; 結束程式
        ```
    - 如果 `AX` 和 `BX` 相等，程式會跳轉到標籤 `equal`，並執行 `MOV CX, 0`。
    - 如果 `AX` 和 `BX` 不相等，程式會繼續往下執行 `MOV CX, 1`，然後跳轉到 `end`。

- **迴圈控制**
    - `loop_start`：標識迴圈的開始位置
        ```assembly
        MOV CX, 5         ; 設定迴圈計數器為 5
        
        loop_start:           ; 定義標籤 loop_start
            DEC CX            ; CX 減 1
            JNZ loop_start    ; 如果 CX 不為 0，跳回標籤 loop_start

        INT 20h           ; 當 CX 為 0 時，結束程式
        ```
    - `JNZ`（Jump if Not Zero）指令會在 `CX` 不為 0 的情況下跳回 `loop_start`，繼續執行迴圈。
    - 當 `CX` 減到 0 時，跳轉結束，程式退出迴圈。

- **函數呼叫與返回**
    - 標籤也可以用來標識函數的入口點，並輔助實現從函數返回的機制。
        ```assembly
        CALL myFunction   ; 呼叫標籤 myFunction 所標識的子程式
        JMP end           ; 跳轉到標籤 end 結束程式

        myFunction:           ; 定義標籤 myFunction
            MOV AX, 1234h     ; 在函數內執行一些操作
            RET               ; 返回到 CALL 指令之後的程式位置

        end:                  ; 定義標籤 end
            INT 20h           ; 結束程式
        ```
    - `CALL myFunction` 會跳轉到 `myFunction` 標籤處，執行該函數內的指令。
    - 當 `RET` 被執行時，程式會返回到 `CALL` 之後的位置，並繼續執行 `JMP end`。

### 1.8 註釋（Comments）

註釋對於程式的可讀性至關重要。單行註釋以分號（;）開始，多行註釋使用 COMMENT 指令：

```assembly
; 這是單行註釋

COMMENT !
這是多行註釋
可以跨越多行
!
```

## 2. 程式結構

```assembly=
TITLE Program Template (Template.asm)

INCLUDE Irvine32.inc
; 這行引入了 Irvine32 庫，提供了許多有用的和宏

.data
; 在這裡定義變數

.code
main PROC
    ; 主程式邏輯在這裡

    exit
main ENDP

END main
```

**逐行解釋一下ㄅ：**

1. `TITLE`：這是一個指令，為程式提供一個描述性標題。
2. `INCLUDE Irvine32.inc`：這行引入了 Irvine32 庫，它提供了許多函式（如 `WriteString`, `ReadInt` 等）和常量定義。
3. `.data`：開始了數據段，我們在這裡定義變數。
4. `.code`：開始了代碼段，我們的程式邏輯放在這裡。
5. `main PROC`：定義了主程序過程。PROC 表示程序的開始。
6. `exit`：這是 Irvine32 庫提供的[宏](https://shihyu.github.io/books/ch21s02.html)，用於正確地終止程式。
7. `main ENDP`：標誌著主程序過程的結束。
8. `END main`：告訴組譯器程式從 main 過程開始執行。

### 2.1 數據定義

在 `.data` 段中，我們可以定義各種類型的數據。以下是一些常見的數據類型：

```assembly=
.data
byteVar    BYTE    10          ; 8位無符號整數
sbyteVar   SBYTE   -10         ; 8位有符號整數
wordVar    WORD    1000h       ; 16位無符號整數
swordVar   SWORD   -1000h      ; 16位有符號整數
dwordVar   DWORD   12345678h   ; 32位無符號整數
sdwordVar  SDWORD  -12345678h  ; 32位有符號整數
qwordVar   QWORD   1234567812345678h  ; 64位整數
realVar    REAL4   3.14        ; 32位單精度浮點數
doubleVar  REAL8   3.14159     ; 64位雙精度浮點數
stringVar  BYTE    "Hello, World!", 0  ; 以空字節結尾的字符串
```

- 每行的格式為：**`[名稱] [數據類型] [初始值]`**
- 注意字符串是如何定義的：它是一系列的 BYTE，以 0（空字節）結尾。這是 C 風格的字符串表示方法。

### 2.2 基本指令

現在讓我們看一些基本的組合語言指令：

```assembly=
.code
main PROC
    mov eax, 5        ; 將立即數 5 移動到 EAX 暫存器
    add eax, 10       ; 將 10 加到 EAX 中
    sub eax, 3        ; 從 EAX 中減去 3
    mov ebx, eax      ; 將 EAX 的值複製到 EBX
    
    call WriteInt     ; 呼叫 Irvine32 庫函數來輸出 EAX 中的值
    
    exit
main ENDP
END main
```

- `mov`：移動數據。格式為 `mov destination, source`
- `add`：加法。格式為 `add destination, source`
- `sub`：減法。格式為 `sub destination, source`
- `call`：呼叫一個過程。這裡我們呼叫了 Irvine32 庫中的 `WriteInt` 過程來輸出 EAX 中的值。

### 2.3 Irvine32 庫常用函數

Irvine32 庫提供了許多有用的函數，使輸入輸出和其他常見操作變得更容易。以下是一些常用函數：

- `WriteString`：輸出一個以 0 結尾的字符串
- `WriteInt`：輸出 EAX 中的有符號整數
- `WriteDec`：輸出 EAX 中的無符號整數
- `WriteHex`：以十六進制形式輸出 EAX 中的值
- `ReadInt`：從鍵盤讀取一個有符號整數到 EAX
- `ReadString`：從鍵盤讀取一個字符串

讓我們看一個使用這些函數的例子：

```assembly=
INCLUDE Irvine32.inc

.data
prompt BYTE "Please enter a number: ", 0
result BYTE "The number you entered is: ", 0

.code
main PROC
    ; 輸出提示
    mov edx, OFFSET prompt
    call WriteString

    ; 讀取用戶輸入
    call ReadInt

    ; 輸出結果
    mov edx, OFFSET result
    call WriteString
    call WriteInt

    exit
main ENDP
END main
```

這個程式提示用戶輸入一個數字，然後將這個數字輸出。

- `mov edx, OFFSET prompt`：將 prompt 字符串的地址載入 EDX 暫存器。
- `call WriteString`：輸出 EDX 指向的字符串。
- `call ReadInt`：讀取用戶輸入的整數到 EAX。
- `call WriteInt`：輸出 EAX 中的整數。

### 2.4 再多一個範例

```assembly=
INCLUDE Irvine32.inc

.data
count DWORD 5
message BYTE "Hello, Assembly!", 0

.code
main PROC
    mov ecx, count    ; 將循環次數載入 ECX

L1:                   ; 循環標籤
    mov edx, OFFSET message
    call WriteString
    call Crlf         ; 輸出換行
    loop L1           ; 減少 ECX 並跳轉回 L1，直到 ECX 為 0

    exit
main ENDP
END main
```

這個程式會輸出 "Hello, Assembly!" 5 次。

- `L1`：定義了一個標籤，我們可以跳轉到這個位置。
- `loop L1`：是一個特殊的指令，它會減少 ECX 的值並跳轉回 L1 標籤，除非 ECX 變為 0。

---
*註: 部分內容截自於網路，此筆記非完全原創。*  
***Latest Updated On：2024.10.28,   
published with the of LICENSE of WTFPL  
Author：Qaron（呱呱）***