2008年1月27日 星期日

PC Assembly Language 學習筆記(2) - Assembly Language

Machine Language

每種 CPU 僅懂得自己的 machine language,而在 machine language 中的指令相當艱澀難懂不容易閱讀,每個指令都有屬於自己的唯一識別碼,稱為 operation code(或是 opcode),通常位於指令的開頭。

而要讓 CPU 執行工作,必須給他 machine language 才可;然而,直接撰寫 machine code 幾乎是不可能的事情,因此,我們需要可以將 assembly 轉為 machine code 的工具 - assembler


Assembly Language

assembly 程式是以純文字的方式進行儲存的,其中類似的語法如下:
add eax, ebx
由程式中可以看得出來該行指令所要進行的動作,而將其轉譯成 machine code 的工作,則交給 assembler 來做即可。

而在其他的程式語言中,扮演此種角色(將程式轉為 machine code)的,稱為 compiler,然而 compiler 的複雜度遠遠高過 assembler,原因是因為 assembly code 與 machine code 的關係是一行對一行的,而一般程式語言中的一行就常常產生多行相對應的 machine code。

另外與其他程式語言不同的地方是,每種 CPU 不但有自己的 machine language,也有自己的 assembly language,因此若要將 assembly code 進行 porting,難度會比一般的程式語言高上許多。


Instruction operands (指令運算元)

一般 machine code 的指令中都會包含多個或多種的 operand(運算元),而 operand 大概可以分為以下幾類:
  1. register
    指向儲存於 CPU register 中的植。

  2. memory
    指向儲存於 memory 中的資料,而 memory address 有可能是直接寫於指令中,或是由 register 的值所組成。

  3. immediate
    列於指令中的固定值,儲存於指令本身(在 code segment 中),而非在 data segment 中。

  4. implied
    此種 operand 不會明確的顯示。例如:將某個 register 中的值加 1。


Basic instruction (基本指令)

在程式開發中,最常用到的應該是 mov 指令了,其作用在於進行資料的搬移,語法如下:
mov dest, src
其中 src 中的資料會被複製到 dest 中,而 mov 的使用必須符合以下規則:
  1. 不論是那個 operand(dest 或 src),都不能是 memory address
  2. 兩個 operand 都必須相同大小 (表示 AX 中的值不能 mov 到 BL 中)

以下用一些範例來說明:
mov eax, 3  ;設定 EAX register 的值為 3
mov bx, ax ;將 AX register 的值複製到 BX register

add eax, 4 ; eax = eax + 4
add al, ah ; al = al + ah

sub bx, 10 ; bx = bx - 10
sub ebx, edi ; ebx = ebx - edi

inc ecx ; ecx++
dec dl ; dl--


Directive

Directive 則是人自己手動加入的,而不屬於 CPU 的指令,作用有兩種:
  1. 指示 assembler 要作某些事情
  2. 告知 assembler 某些訊息

而這個部分,並不會被 assembler 轉為 machine code,一般常見的 directive 用法大概有幾種:
  1. 定義常數(constant)
  2. 定義儲存資料的記憶體位置
  3. 將多段記憶體空間組合為 segment
  4. 視情況 include 外部的 source code
  5. include 其他檔案

以下介紹幾種 directive:

The equ directive

用來定義 symbol (有名稱的常數,可用於程式中),而 symbol 定義好之後,其值不可再度變更,語法如下:
symbol equ value

The %define directive

此與 C 語言中的 #define 是很像的,大多用來定義 macro 之用,以下用個簡單範例說明:
%define SIZE 100    ; 定義 macro SIZE 為 100
mov eax, SIZE ; 將 100 存於 EAX register 中
%define 是用來定義 macro 的,與 symbol 不同的是,macro 的值可以變更,且使用上不僅限於存單一值而已,還可以作較為複雜的運算。

Data directives

用於 data segment 中,來保留記憶體空間。使用方式有兩種:
  1. 使用 RESX directive,不給予初始值
  2. 使用 DX directive,給予初始值

其中 X 的部分代表所要保留的記憶體空間大小,分別有 B(byte)、W(word)、D(double word)、Q(quad word)、T(ten byte) .... 等等,而每個記憶體空間都會用一個 label 名稱作為標記以方便使用,以下用簡單範例來說明:
L1  db      0       ; 標記為 L1,大小為 1 byte,初始值:0
L2 dw 1000 ; 標記為 L2,大小為 1 word(2 bytes),初始值:1000
L3 db 110101b ; 標記為 L3,大小為 1 byte,初始值:110101(2進位,10進位為 53)
L4 db 12h ; 標記為 L4,大小為 1 byte,初始值:12(16進位,10進位為 18)
L5 db 17o ; 標記為 L5,大小為 1 byte,初始值:17(8進位,10進位為 15)
L6 dd 1A92h ; 標記為 L6,大小為 1 double word(4 bytes),初始值:1A92(16進位)
L7 resb 1 ; 標記為 L7,大小為 1 byte,未初始化
L8 db "A" ; 標記為 L8,大小為 1 byte,初始值:A(10進位為 65)
以上定義的 7 個記憶體空間,將會是連續的記憶體空間。

連續的記憶體空間還可以用以下方法配置:
L9  db  0, 1 , 2, 3             ; 連續定義 4 個 byte
L10 db "w", "o", "r", "d", 0 ; 定義字串為 word (結束為 \0)
L11 db 'word', 0 ; 同上

若要配置更大的記憶體空間,可以用 TIMES directive 來重複執行:
L12 times   100 db 0    ; 相同於定義 100 次 db 0
L13 resw 100 ; 保留 100 個 word 大小的記憶體空間

當所需要的記憶體空間都配置好後,再來就是使用的問題了,而使用時必須透過 label 來使用,方式有兩種:
  1. 當直接使用 label 名稱時,表示使用的是該記憶體空間的 memory address
  2. 若是 label 加上兩個中括號(例如:[L1]),表示所使用的為該記憶體空間中儲存的值
其實可以將其想像成 C 的 pointer,這樣應該不難了解。

以下有一些使用範例說明:
mov al, [L1]    ; 複製 L1 的值到 AL register
mov eax, L1 ; EAX register 中的值為 L1 的 memory address
mov [L1], ah ; 複製 AH register 中的值到 L1
mov eax, [L6] ; 複製 L6 的值到 EAX register
add eax, [L6] ; EAX = EAX + L6 的值
add [L6], eax ; L6 = L6 + EAX register 中的值
; AL register 大小為 8 bits
; L6 大小為 32 bits (double word)
mov al, [L6] ; 複製 L6 中的前 8 bits 到 AL register
注意最後一行,在 NASM 中,是不會顯示錯誤的,assembler 僅會檢查 lable 是否有使用錯誤的情況發生而已;而這是為了確保若是 lable 沒有加上中括號(表示為 memory address)時可以正常的使用。

然而,其實 assembler 的檢查也蠻嚴格的,以下用的範例說明:
; 這是不行的,會發生 operation size not specified error
; 因為 assembler 不知道要把 1 當成 byte, word, 還是 double word
mov [L6], 1 ; 儲存 1 到 L6

; 所以要指定 size,改成如下:
mov dword [L6], 1
; 表示要將 1 視為 double word 存入(其他如:byte, word, qword, tword)


Input & Output

在高階語言中,都會提供與 I/O 相關的標準 library 可供使用,但在 assembly 中並沒有;因此 assembly 要執行 I/O 相關工作,不是直接存取硬碟,就是必須使用 OS 所提供的功能。

一般來說,在 assembly 中會常常使用到 C 的 I/O library,也是為了標準化的關係。

5 則留言:

  1. 可以告诉我,如果要把0+1+2+3+...+100的assembly language 应该要怎么写?要用DB还是DW啊?

    回覆刪除
  2. 要寫 0 加到 100 ..... 可以用 %REP directive 來作....

    回覆刪除
  3. 謝謝版主,很有幫助的筆記!

    回覆刪除
  4. 非常謝謝這篇詳盡的解說!!!!

    回覆刪除