2008年2月11日 星期一

PC Assembly Language 學習筆記(5) - Control Structures

Comparisons

在 assembly 中,若要進行資料的比較,必須使用 CMP 指令,以下是 CMP 指令的使用方式:
cmp vleft, vright
CMP 指令作的,其實就是 vleft - vright,並將比較後的結果存入 EFLAGS register 中(使用到 zero、carry、sign 三個 flag),以下進行詳細的說明:

unsigned integer

對於 unsigned integer 的比對,會使用到 ZF(zero falg) 與 CF(carry flag) 兩個 flag,會有以下三種情況:
  1. vleft = vright
    相減後結果為 0,因此 ZF=1;不會有借位的情形,因此 CF=0
  2. vleft > vright
    相減後結果大於 0,因此 ZF=0;不會有借位的情形,因此 CF=0
  3. vleft < vright
    相減後結果小於 0,因此 ZF=0;會有借位的情形,因此 CF=1

signed integer

對於 signed integer 的比對,則會使用到 ZF(zero flag)、OF(overflow flag) 與 SF(sign flag) 三個 flag,也是會有三種情況:
  1. vleft = vright
    相減後結果為 0,因此 ZF=1;不會有借位的情形,因此 CF=0;結果不為負,因此 SF=0
  2. vleft > vright
    相減後結果大於 0,因此 ZF=0;且 CF=SF
  3. vleft < vright
    相減後結果小於 0,因此 ZF=0;且 CF<>SF

當然會改變 EFLAGS 內容的不僅是 CMP 指令而已,不過若是要進行類似 if 的動作,就必須使用上面的方式。


Branch instructions

branch 指令在 assembly 中,作用就類似高階語言中的 goto,型態有分為兩種,分別是:
  1. unconditional branch
    類似 goto,沒有任何條件判斷就會進行跳躍
  2. conditional branch
    根據 EFLAGS register 中的特定 flag 來決定是否進行跳躍

在 unconditional branch 中,最常用的即為 JMP(Jump) 指令,可以指定跳躍至哪一個 code label 所在的位置,而 JMP 有分為不同三種類型:
  1. SHORT
    此種僅能從所在位置前後跳躍 128 bytes 長度的空間,僅使用一個 signed byte 儲存跳躍的長度與位置,優點是節省記憶體空間。
  2. NEAR
    這是 unconditional/conditional branch 所預設的跳躍類型,可跳躍至同一個 code segment 中的任何空間,最大可跳躍 2(28*2)bytes 或 4 bytes(28*4預設) 可表示長度的空間。
    另外若是指定要跳躍 2 bytes 可表示長度的空間,必須在 JMP 與 code label 之間放入 WORD 關鍵字。
  3. FAR
    可跳躍至不同的 code segment。

接著說到 conditional branch,是依靠在 EFLAGS register 中的特定 bit 來決定是否進行跳躍的,以下用張簡單的圖表來說明:

其中 ZF(zero flag)、OF(overflow flag)、SF(signed flag)、CF(carry flag) 之前都已經有提過,而 PF(parity flag) 則是根據計算結果的 lower 8 bits 中,有奇數或偶數個 bit 被設定為 1 來決定是否設定此 flag。

以下用幾段簡單的範例程式來說明:
;高階程式的撰寫方式
if(EAX == 0)
EBX = 1;
else
EBX = 2;

;轉換為以下的 assembly code
cmp eax, 0 ;比較 EAX 與 0(若相同則 ZF=0,不相同則 ZF=1)
jz thenblock ;ZF=0,跳躍至 thenblock
mov ebx, 2 ;EAX<>0,因此 EBX=2
jmp next ;if 判斷完畢
thenblock:
mov ebx, 1 ;EAX=0,因此 EBX=1
next:
;高階程式的撰寫方式
if(EAX >= 5)
EBX = 1;
else
EBX = 2;

;轉換為以下的 assembly code
cmp eax, 5 ;比較 EAX 與 5(若 EAX=5 則 ZF=1;EAX>5 則 ZF=0, CF=SF)
js signon ;SF=1
jo elseblock ;SF=0, OF=1 (表示 EAX < 5)
jmp thenblock ;SF=0, OF=0 (表示 EAX >= 5)

signon:
jo thenblock ;SF=1, OF=1 (表示 EAX > 5)
elseblock:
mov ebx, 2
jmp next
thenblock:
mov ebx, 1
next:

從上面程式看得出來,真的不是很容易理解,更別說是撰寫了,不過幸好 80x86 提供了一些更簡單的寫法,以下用一張圖表來說明:

以下舉個例子,相信上面的表會比較容易看懂。
#JL(Jump if less than) 等於 JNGE(jump if not greater than and not equal to)
x < y => not(x >= y)
因此上面的程式修改如下:
;高階程式的撰寫方式
if(EAX >= 5)
EBX = 1;
else
EBX = 2;

;轉換為以下的 assembly code
cmp eax, 5
jge thenblock
mov ebx, 2
jmp next
thenblock:
mov ebx, 1
next:


The loop instructions

迴圈(loop)在程式開發中是絕對不可或缺的一環,在 assembly 中,loop 的使用並不困難,指令大致有以下三種:
  1. LOOP
    ECX - 1,若 ECX <> 0,繼續下一個迴圈
  2. LOOPELOOPZ
    ECX - 1,若 ECX <> 0 且 ZF=1,繼續下一個迴圈
  3. LOOPNELOOPNZ
    ECX - 1,若 ECX <> 0 且 ZF=0,繼續下一個迴圈

後兩個 loop 常用於 sequential search 中。

以下用一段範例程式來說明:
;高階程式的撰寫方式
sum = 0;
for(i = 10 ; i > 0 ; i--)
sum += i

;轉換為以下的 assembly code
mov eax, 0
mov ecx, 10
loop_start:
add eax, ecx
loop loop_start


Translating Standard Control Structures

以下要用幾個範例來展示如何將高階語言中的流程控制語法轉換為 assembly code:

IF
;高階程式的撰寫方式
if( condition )
then_block;
else
else_block;

;轉換為以下的 assembly code(兩個分號表示程式擺放的位置)
;;此處擺放用來設定 EFLAGS register 內容的程式
;;例如:cmp xx, xx
jxx else_block ; 判斷為 false
;; 判斷為 true 後所要執行的程式碼
jmp end_if
else_block:
;; 判斷為 false 後所要執行的程式碼
end_if:
while loops
;高階程式的撰寫方式
while( condition ) {
迴圈執行的內容;
}

;轉換為以下的 assembly code(兩個分號表示程式擺放的位置)
while:
;;此處擺放用來設定 EFLAGS register 內容的程式
;;例如:cmp xx, xx
jxx end_while ; 判斷為 false,結束迴圈
;; 迴圈執行的內容
jmp while
end_while:
do while loops
;高階程式的撰寫方式
do {
迴圈執行的內容;
} while( condition );

;轉換為以下的 assembly code(兩個分號表示程式擺放的位置)
do:
;; 迴圈執行的內容
;;此處擺放用來設定 EFLAGS register 內容的程式,例如:cmp xx, xx
jxx do ; 判斷為 true,繼續執行迴圈


範例程式(求質數)

接著介紹求質數的範例,以下為 C code:


接著以下為 assembly code:
;檔案: prime.asm
;描述:求質數

%include "asm_io.inc"

;初始化資料
segment .data
Message db "Find primes up to: ", 0

;未初始化資料
segment .bss
Limit resd 1 ;尋找質數的上限
Guess resd 1 ;目前正在處理判斷的數值

segment .text
global asm_main
asm_main:
enter 0, 0 ;程式開始
pusha

mov eax, Message ;印出訊息
call print_string
call read_int ;讀取使用者輸入的數值,存入 Limit
mov [Limit], eax

mov eax, 2 ;印出 2 與 3
call print_int
call print_nl
mov eax, 3
call print_int
call print_nl

mov dword [Guess], 5

while_limit:
mov eax, [Guess]
cmp eax, [Limit] ;比較 Guess 與 Limit
jnbe end_while_limit ;Guess > Limit,程式結束

mov ebx, 3 ;在 EBX 中設定第一個因數(factor)為 3

while_factor:
mov eax, ebx
mul eax ;EDX:EAX = EAX * EAX
jo end_while_factor ;EAX 相乘後的值無法存入 32-bit 的 EAX register(註解也無所謂,只要不要輸入過大的值即可)
cmp eax, [Guess]
jnb end_while_factor ;if !(factor * factor < guess)
mov eax, [Guess]
mov edx, 0
div ebx ;EDX = EDX:EAX % EBX
cmp edx, 0 ;檢查餘數是否為 0
je end_while_factor ;if(guess % factor == 0)

add ebx, 2 ;factor += 2
jmp while_factor

end_while_factor:
je end_if
mov eax, [Guess] ;印出質數
call print_int
call print_nl

end_if:
add dword [Guess], 2 ;guess + 2
jmp while_limit

end_while_limit:
popa
mov eax, 0 ;回到 C 程式
leave
ret

3 則留言: