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