# 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(呱呱)***