WFU

[ 精選文章 ]

自行車 入門 Escape 3 , Snap 21 , Revel

最近周末想運動 , 平日想通勤 , 想買台自行車 , 把自己找的資料跟大家分享 , 如果你是玩家級的 就不用看了 這是給跟我一樣的新新新手 參考的 騎車半年後的補充: 如果你有把握你是真的有時間有興趣會一直騎,建議還是存點錢買好一點的彎把公路車, 或是可以考慮買2手的自...

2015年10月14日 星期三

組合語言指令介紹

REF: http://masm1215.myweb.hinet.net/

組合語言的假指令,可分為「一般的假指令」、「條件式組譯」及「高階指令」等三種,而一般,我們只需用到第一種,至於第二種,則式各人需要使用於巨集檔中,第三種,則不建議使用,因為要使用高階指令,那你不如用C語言去寫就好了,何必學組合語言呢,整個程式的整體架構是由程式設計師一人開發的,假如你加入了高階指令,那麼這個組合語言程式,不就沒有了他的本質(速度快,檔案小)。
  以下會將這三種假指令取其常用的,分別一一為您介紹。
捷徑:條件式組譯高階指令

一般的假指令

一般假指令的種類很多,例如:.model, .code, .data, .stack, end, ;, .186, .286, .386, .486, .586, equ, org, label, offset, segment, .data?, const, @, .fardata, .fardata?, seg, assume,dup等等。
  假指令本身,並不是程式碼,而是您在組譯時,組譯程式(MASM)會去看得懂這些假指令,而加以處理你所寫的程式碼,我將所有會用到的指令分別介紹如下:
1.指定微處理器模式 .8086、.186、.286、.386、.486
  若指定.286,那麼程式在組譯時,就無法組譯386以上的組合語言指令碼,例如 EAX與 EBX 等32位元的暫存器,只有386以上CPU才有,所以若程式裡有 mov eax,eax 等指令時,組譯會發生組譯錯誤的訊息,唯有將微處理器模式改回 .386 或 .486 時才會組譯成功。所以小弟建議,一般都採用 .486 即可。功能就是將以上的程式碼組譯成 80486 的程式碼。
2.定義記憶體模式 .model [模式」
  於本站「組合語言基礎」=>『組合語言原始檔基本架構』中,欲編寫組合語言程式一定要在原始檔中定義 記憶體模式,而其模式的種類與使用場合分別於下說明
記憶體模式
用途及記憶體使用範圍
tiny用於成立 .com 檔,程式段、資料段、額外段及堆疊段等四個區共用在同一個區段(64Kb)中,檔案最大僅能64Kb。
small程式段及資料段,各有64Kb的空間。為一般最常用的 .exe 檔的模式,建議使用。
medium程式段可超過64Kb,而資料段只有64Kb的空間。
compact資料段可超過64Kb,而程式段只有64Kb的空間。
large程式段與資料段,皆可大於 64 Kb。
huge同上,但更強的是,連同資料段中的任意某陣列,也可大於 64Kb 的範圍。
flat???? 用於 OS/2 系統。用途不詳......
使用範例
.model tiny

.model small
3.程式區段的起始 .code [名稱]
  定義程式區段的起始,如果我們定義兩個以上的程式段時,就必需為這些程式段命不同的名字;當我們只定義一個程式區段時,名稱可以省略不必命名,但在 tiny、small、compact 三種模式下,只能有一個程式段,所以就不必名稱了。
4.堆疊段起始 .stack [size]
  定義堆疊段的起始,定義時可指定大小,其中以 byte 為單位,若省略而不指定大小時,會以 1024 bytes 取代之。
5.一般資料段起始 .data、.fardata [名稱]
  近程資料段以 .data 起始,但其超出 64Kb 範圍的資料段稱為遠程資料段需以 .fardata 起始,而名稱,只有在定義兩個以上的遠程資料段時,才需命名。
6.無初始值之資料段與常數資料段 .data?、.fardata?、.const
  我們如果只編寫單獨為組合語言程式時,這三個區段則無需定義,但若我們寫的程式要與高階語言作連結時,就必需將無初始值的資料移至 data? 或 fardata? 區段中,且需將用來定義字串、實數及常數的部份移至 .const 區段中。如下
.model small
.486
.code
       :
       :
.data
       :
.data?
mag01 db ?    ; 單一變數,無初始值,單位 byte
mag02 dw ?,?,?  ; 一般變數,無初始值,單位 word 共 3 word = 6 bytes
mag03 db 24 dup(?) ; 陣列,無初始值,容量大小為 24 bytes
 
.stack
end
7.結束整個模組的組譯 END [程式段起始位址標名]
  當我們寫程式區段的程式碼時,必需在啟始執行的位置設一個標名,那麼該程式被組譯後,程式就會從這個標名開始執行,假設我們把它放在程式段的第一列。那麼於 END 之後也必需加上這個標名,才算是指定成功,程式才會以這個標名做起始,如下:
.model tiny
.486
.code
begin0: mov ax,ds
                mov  ......
     :
 
begin1: ......
 
.exit
 
 
 
 
end  begin0   ; 在此指定 .code 段的起始位址 cs:ip 值為 begin0
8.各區段暫存器之段位址初值的取得 @code、@data、@stack、@data? .....
  利用 mov 指令,即可將各區段段位值址取得,方法如下
   :
.stack 100h
   :
.data
   :
.code
start0:
   :
   mov ax,@code ; 取出 .code 段的區段位址,存入 ax
   mov bx,@data ; 取出 .data 段的區段位址,存入 bx
   mov dx,@stack ; 取出 .stack 段的區段位址,存入 dx
   :
   end start0
程式開始執行時,cs=@code 、ss=@stack、sp=100h(因為如上的設定100h),而 ds 與 es 則是指向另一個程式前置區(psp),所以 ds 與 es 的值要由我們自行在程式段中寫程式來指定,用 mov ax,@data 與 mov ds,ax 來讓 ds 指向資料段,如此,我們才取得到資料段中所存的資料、變數,所以我們可以將程式的基本架構寫成如下的格式。
.model small
.486
.code
begin0:  mov ax,@data
    mov ds,ax
 
 
 
.exit
.data
 
 
 
 
.stack
end   begin0
9.程式段起始位址的假指令 .startup
  使用時必需省略如上中的 end 後面的 begin0 及程式段的 begin0:,而且 .startup 也會自動執行 mov ax,@data 及 mov ds,ax,但如果是 .com 檔,它會自己加入 org   100h 指令,所以修改後架構,可參考「組合語言基礎」=>『組合語言原始檔基本架構』。
  也就是說用於 .com 檔時,可做下列改變
    :
.code
org  100h
start:
    mov .....     ; 程式開始
    :
    end  start ; 程式結束
轉換為
    :
.code
.startup
    mov ...... ; 程式開始 ( start 標名不見了,org 100h 也可省略了)
    :
    end   ; 程式結束
然而用於 .exe 檔時,與上有所不同,如下
    :
.code
start:
    mov ax,@data
    mov ds,ax
    mov ...... ; 程式開始
    :
    end  start ; 程式結束
轉換為
    :
.code
.startup
    mov ...... ; 程式開始 ( start 不見了,ds指向資料段也自動執行了)
    :
    end   ; 程式結束
10.程式結束返回 DOS 之假指令 .exit [錯誤碼](errorlevel)
  組譯程式在碰到 .exit 時,會自動翻譯為執行下列程式碼
    mov ah,4ch
    mov al,[錯誤碼]  ; <= 若無輸入錯誤碼,則此列將省略不會執行
    int 21h
上面的功能是將程式結束,主控權交還給 DOS。
11.重覆定義資料 dup
  可以重覆定一指定個數的記憶體變數內容,例如我們要定一個名稱為 poss0的記憶體變數,而內容是18個空白鍵,那麼我們的定義方式,可以有以下幾種為例:
方法一:
poss0 db 20h,20h,20h,20h,20h,20h,20h,20h,20h
   db 20h,20h,20h,20h,20h,20h,20h,20h,20h
方法二:
poss0 db 18 dup(20h)
12.常數符號之定義 equ 及 "="
  可利用定義功能,將程式中的某數值,以一個名稱取代,這種定義的用途的好處,就是修改程式容易,當我們寫一個程式,但如檔案內有個A數值為98,我們將它用等號定義,名稱定義為 numa,有一天我們要修改程式的A數值時,我們可以直接從定義的部份更改其A值即可將整個檔的A值都改了,這樣方便吧,如下:
numa equ 98
numa = 98
至於 equ 與 = 的差別,有兩個地方不同,第一就是 = 可以重覆定義同一個符號,但 equ 卻不行,第二就是 equ 可以定義字串等值,也就是定義變數的別名,但 = 卻不行。如下說明:
.model small
.code
.startup
 
aaa = 23
aaa = aaa+2
 
bbb equ 23
bbb equ bbb+2   ; 這裡會有錯,equ不可重複定義同一符號
 
ccc equ <aaa+bbb>
ddd equ <ax,ccc>
 
eee = <aaa+bbb>  ; 這裡有錯,"=" 不能定義字串或變數別名
 
   mov ax,aaa
   mov bx,bbb
   mov cx,ccc
   mov ddd
.exit
org, label,offset, segment,@,seg, assume,include等等。

條件式組譯

條件式組譯通常用在巨集檔裡面,因為同一個巨集中,為了避免佔用太大的記憶體,所以不是整個巨集裡的內容都需要用到,那麼那些沒有用到的部份就可不必組譯,可節省記憶體空間。
1.假設指令集 if....(else)....endif
  使用格式 [ ifxx   條件運算式 ],其中 ifxx 可為以下各模式,當條件成立時,才會將 ifxx 與 endif 之間的內容組譯。
if 假如不等於 0
ife 假如等於 0
ifdef 假如已定義過
ifndef 假如沒定義過
ifb 假如為空白
ifnb 假如不為空白
ifidn A,B 假如A等於B
ifidni A,B 假如A等於B(但區分大小寫)
ifdif A,B 假如A不等於B
ifdifi A,B 假如A不等於B(但區分大小寫)
使用範例如下
abcd  macro   pra1,pra2    ifb <para1>  ; 假如引數1沒有輸入任何東西,則令 ax =1
    mov   ax,1
    endif
     :
    exitm
2.條件二 else 指令集
  else、elsee、elsedef、elseb、elsenb、...... 功能同上,需用在 if 之後。
3.重覆組譯指令 repeat.....endm
  使用的格式為,[repeat 次數],當組譯器在組譯時,就會重複組譯 repeat 與 endm 之間的內容,所以通常用於巨集指令之內。
4.條件重覆組譯 while.....endm
  使用格式為 [while   條件運算式],當條件運算式成立時,重複組譯 while 與 endm之間的程式敘述。
5.重覆參數組譯 for.....endm
  使用格式為 [ for   參數,<數值1,數值2,數值3.....> ],重複組譯的次數是取決於共輸入幾個數值,如果只輸入三個數值,代表藉於 for 與 endm 之間的程式碼重複組譯三次,每一次的參數值都不一樣,第一次時參數是放入數值1,第二次是放人數值2,第三次是放人數值3。
6.重覆文字參數組譯 forc
  使用格式為 [ forc   參數,<字串> ],重複組譯的次數是取決字串的長度,如果只輸入 345 三個字,代表藉於 forc 與 endm 之間的程式碼重複組譯三次,每一次的參數值都不一樣,第一次時參數是放入數值3,第二次是放人數值4,第三次是放人數值5。 

高階指令

高階指令,也可稱為流程控制指令,可以讓使用者在寫迴圈時更為簡便化,且由於它的語法與 C 語言相近,所以語法也就比較高階化,讓人容易一看就知道在寫什麼。
  而可用的高階指令有 .if、.while、.repeat、.break、.continue 等五種,使用的語法分別列出如下:
(1)  .if 的使用方式
  下面是簡單的 .if 控制流程,首先會檢查條件式 A 是否有成立,如果有成立則往下一行執行,執行完 .if 的部份再跳至 .endif 離開迴圈,如果不成立則跳到下一個條件式 B 去檢查是否成立,依此類推,直到每個條件式都不成立時,才會去執行 .else 裡面的東西,執行完一樣是跳至 .endif 離開迴圈。
.if 條件式 A   :
.elseif 條件式 B
   :
.elseif 條件式 C       ( 可以有無限多個 .elseif )
   :
.else
   :
.endif
(2) .while 之使用方式
  條件式 A如果為真,則執行 .while ....   .endw 之間的指令的部份,直至條件式 A為假,才停止執行。
.while 條件式A   :      (假如條件式A為真則執行此處指令)
.endw
 (3) .repeat 之使用方式
  有兩種使用方法,第一種是 .repeat 與 .until 配合,即當執行裡面的指令一次後,去判斷條件式 A是否為真,如果為假則繼續重覆此段的指令,如果為真則結束迴圈,如下
.repeat  :   (此段指令即欲執行之迴圈)
.until 條件式A
第二種方法,是 .repeat 與 .untilcxz,即當執行完裡面的指令一次後,去判斷條件式A是否為真,且亦去判斷 cx 是否為 0,如果條件式A為假且cx不為0則繼續重覆此段的指令,如果為條件式A為真或者 cx=0 則結束迴圈,如下
.repeat  :   (此段指令即欲執行之迴圈)
.untilcxz 條件式A
(4) .break 與 .continue
  .break為結束迴圈,而 .continue 則為跳至迴圈的開頭,如下:
.while .....   :
.break
   :
.endw
     <===  執行至 .break時,會直接結束迴圈,而從這開始執行
 
 .while .....  <=== 執行至 .continue 時,會跳回這裡開始執行
   :
.continue
   :
.endw
.break 與 .continue 也能與 .if 搭配使用,如下
.while ......   :
.continue .if 條件式A <=== 條件式A成立時,跳至迴圈開頭
   :
.break .if 條件式B <== 條件式B成立時,結束迴圈
   :
.endw
(5)條件式的使用格式與方法
運算符號
意義
==
!=
>
<
>=
<=
&
!
&&
||
等於
不等於
大於
小於
大於等於
小於等於
AND
NOT
AND
OR

沒有留言:

張貼留言