2007年8月30日 星期四

組合語言超濃縮教學

組合語言超濃縮教程

“ 哎喲,哥們兒,還搗鼓組合語言呢?那東西沒用,兄弟用VB"釣"一個API就夠你忙活個十天半月的,還不一定搞出來。”此君之言倒也不虛,那吾等還有無必要研 他一究呢?(廢話,當然有啦!要不然你寫這篇文章幹嘛。)別急,別急,讓我把這個中原委慢慢道來:一、所有電腦語言寫出的程式運行時在記憶體中都以機器碼方 式存儲,機器碼可以被比較準確的翻譯成組合語言,這是因爲組合語言相容性最好,故幾乎所有跟蹤、調試工具(包括WIN95/98下)都是以組合語言示人的,如 果閣下對CRACK頗感興趣……;二、組合語言直接與硬體打交道,如果你想搞通程式在執行時在電腦中的來龍去脈,也就是搞清電腦每個組成部分究竟在幹什麽、究 竟怎麽幹?一個真正的硬體發燒友,不懂這些可不行。三、如今玩DOS的多是“高手”,如能像吾一樣混入(我不是高手)“高手”內部,不僅可以從“高手”朋 友那兒套些黑客級“機密”,還可以自詡“高手”盡情享受強烈的虛榮感--#$%& “醒醒!”

  對初學者而言,組合語言的許多命令太複雜,往往學習很長時間也寫不出一個漂漂亮亮的程式,以致妨礙了我們學習組合語言的興趣,不少人就此放棄。所以我個 人看法學組合語言,不一定要寫程式,寫程式確實不是組合語言的強項,大家不妨玩玩DEBUG,有時CRACK出一個小軟體比完成一個程式更有成就感(就像學電腦先 玩遊戲一樣)。某些高深的指令事實上只對有經驗的組合語言程式員有用,對我們而言,太過高深了。爲了使學習組合語言有個好的開始,你必須要先排除那些華麗複雜 的命令,將注意力集中在最重要的幾個指令上(CMP LOOP MOV JNZ……)。但是想在囉裏吧嗦的教科書中完成上述目標,談何容易,所以本人整理了這篇超濃縮(用WINZIP、WINRAR…依次壓迫,嘿嘿!)教程。 大言不慚的說,看通本文,你完全可以“不經意”間在前輩或是後生賣弄一下DEBUG,很有成就感的,試試看!那麽――這個接下來呢?―― Here we go!(閱讀時看不懂不要緊,下文必有分解)

  因爲組合語言是通過CPU和記憶體跟硬體對話的,所以我們不得不先瞭解一下CPU和記憶體:(關於數的進制問題在此不提)

  CPU是可以執行電腦所有算術╱邏輯運算與基本 I/O 控制功能的一塊晶片。一種組合語言只能用於特定的CPU。也就是說,不同的CPU其組合語言的指令語法亦不相同。個人電腦由1981年推出至今,其CPU 發展過程爲:8086→80286→80386→80486→PENTIUM →……,還有AMD、CYRIX等旁支。後面相容前面CPU的功能,只不過多了些指令(如多能奔騰的MMX指令集)、增大了寄存器(如386的32位 EAX)、增多了寄存器(如486的FS)。爲確保組合語言程式可以適用於各種機型,所以推薦使用8086組合語言,其相容性最佳。本文所提均爲8086組合語言 語言。寄存器(Register)是CPU內部的元件,所以在寄存器之間的資料傳送非常快。用途:1.可將寄存器內的資料執行算術及邏輯運算。2.存於寄 存器內的位址可用來指向記憶體的某個位置,即定址。3.可以用來讀寫資料到電腦的周邊設備。8086 有8個8位元資料寄存器,這些8位元寄存器可分別組成16位寄存器:AH&AL=AX:累加寄存器,常用於運算;BH&BL=BX:基址寄存 器,常用於位址索引;CH&CL=CX:計數寄存器,常用於計數;DH&DL=DX:資料寄存器,常用於資料傳遞。爲了運用所有的記憶體空 間,8086設定了四個段寄存器,專門用來保存段地址:CS(Code Segment):代碼段寄存器;DS(Data Segment):資料段寄存器;SS(Stack Segment):堆疊段寄存器;ES(Extra Segment):附加段寄存器。當一個程式要執行時,就要決定程式碼、資料和堆疊各要用到記憶體的哪些位置,通過設定段寄存器 CS,DS,SS 來指向這些起始位置。通常是將DS固定,而根據需要修改CS。所以,程式可以在可定址空間小於64K的情況下被寫成任意大小。 所以,程式和其資料組合起來的大小,限制在DS 所指的64K內,這就是COM文件不得大於64K的原因。8086以記憶體做爲戰場,用寄存器做爲軍事基地,以加速工作。除了前面所提的寄存器外,還有一些 特殊功能的寄存器:IP(Intruction Pointer):指令指標寄存器,與CS配合使用,可跟蹤程式的執行過程;SP(Stack Pointer):堆疊指標,與SS配合使用,可指向目前的堆疊位置。BP(Base Pointer):基址指標寄存器,可用作SS的一個相對基址位置;SI(Source Index):源變址寄存器可用來存放相對于DS段之源變址指標;DI(Destination Index):目的變址寄存器,可用來存放相對于 ES 段之目的變址指標。還有一個標誌寄存器FR(Flag Register),有九個有意義的標誌,將在下文用到時詳細說明。

  記憶體是電腦運作中的關鍵部分,也是電腦在工作中儲存資訊的地方。記憶體組織有許多可存放數值的儲存位置,叫“地 址”。8086位址匯流排有20位元,所以CPU擁有達1M的定址空間,這也是DOS的有效控制範圍,而8086能做的運算僅限於處理16位元資料,即只有0到 64K,所以,必須用分段定址才能控制整個記憶體位址。完整的20位地址可分成兩部份:1.段基址(Segment):16位元二進位數字後面加上四個二進位 0,即一個16進制0,變成20位二進位數字,可設定1M中任何一個64K段,通常記做16位二進位數字;2.偏移量(Offset):直接使用16位元二進位 數,指向段基址中的任何一個位址。如:2222(段基址):3333(偏移量),其實際的20位地址值爲:25553。除了上述營養要充分吸收外,你還要 知道什麽是DOS、BIOS功能調用,簡單的說,功能調用類似於WIN95 API,相當於副程式。組合語言寫程式已經夠要命了,如果不用MS、IBM的副程式,這日子真是沒法過了(關於功能調用詳見《電腦愛好者》98年11期)。

  編寫組合語言有兩種主要的方法:1.使用MASM或TASM等編譯器;2.使用除錯程式DEBUG.COM。 DEBUG其實並不能算是一個編譯器,它的主要用途在於除錯,即修正組合語言程式中的錯誤。不過,也可以用來寫短的組合語言程式,尤其對初學者而言,DEBUG 更是最佳的入門工具。因爲DEBUG操作容易:只要鍵入DEBUG回車,A回車即可進行組合語言,過程簡單,而使用編譯器時,必須用到文本編輯器、編譯器本 身、LINK以及EXE2BIN等程式,其中每一個程式都必須用到一系列相當複雜的命令才能工作,而且用編譯器處理根源程式,必須加入許多與指令語句無關的 指示性語句,以供編譯器識別,使用 DEBUG 可以避免一開始就碰到許多難以理解的程式列。DEBUG 除了能夠組合語言程式之外,還可用來檢查和修改記憶體位置、載入儲存和執行程式、以及檢查和修改寄存器,換句話說,DEBUG是爲了讓我們接觸硬體而設計的。 (8086常用指令用法將在每個組合語言程式中講解,限於篇幅,不可能將所有指令列出)。

  DEBUG的的A命令可以組合語言出簡單的COM文件,所以DEBUG編寫的程式一定要由地址 100h(COM文件要求)開始才合法。FOLLOW ME,SETP BY SETP(步步回車):

  輸入 A100 ; 從DS:100開始組合語言
  2.輸入 MOV DL,1 ; 將數值 01h 裝入 DL 寄存器
  3.輸入 MOV AH,2 ; 將數值 02h 裝入 DL 寄存器
  4.輸入 INT 21 ; 調用DOS 21號中斷2號功能,用來逐個顯示裝入DL的字元
  5.輸入 INT 20 ; 調用DOS 20號中斷,終止程式,將控制權交回給 DEBUG
  6.請按 Enter 鍵
  7.現在已將組合語言程式放入記憶體中了,輸入 G(運行)
  8.出現結果:輸出一個符號。
  ㄖ ←輸出結果其實不是它,因WORD97無法顯示原結果,故找一贋品將就著。
  Program terminated normally

  我們可以用U命令將十六進位的機器碼反組合語言(Unassemble)成組合語言指令。你將發現每一行右邊的組合語言指令就是被組合語言成相應的機器碼,而8086實際上就是以機器碼來執行程式。
  1.輸入 U100,106
  1FED:0100 B201 MOV DL,01
  1FED:0102 B402 MOV AH,02
  1FED:0104 CD21 INT 21
  1FED:0106 CD20 INT 20
  DEBUG可以用R命令來查看、改變寄存器內容。CS:IP寄存器,保存了將執行指令位址。
  1.輸入R
  AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
  DS=1FED ES=1FED SS=1FED CS=1FED IP=0100 NV UP EI PL NZ NA PO NC
  1FED:0100 B201 MOV DL,01

  當程式由DS:100開始執行,那麽終止程式時,DEBUG會自動將IP內容重新設定爲100。當你要將此程式做成一個獨立的可執行文件,則可以用N命令對該程式命名。但一定要爲COM文件,否則無法以DEBUG載入。
  輸入N SMILE.COM ;我們得告訴DEBUG程式長度:程式從100開始到106,故佔用7
  ;位元組。我們利用BX存放長度值高位部分,而以CX存放低位元部分。
  2.輸入RBX ;查看 BX 寄存器的內容,本程式只有7個位元組,故本步可省略
  3.輸入 RCX  ;查看 CX 寄存器的內容
  4.輸入 7  ;程式的位元組數
  5.輸入 W ;用W命令將該程式寫入(Write)磁片中

  修行至此,我們便可以真正接觸8086組合語言指令了。 當我們寫組合語言程式的時候,通常不會直接將機器碼放入記憶體中,而是打入一串助記符號(Mnemonic Symbols),這些符號比十六進位機器碼更容易記住,此之謂組合語言指令。助記符號,告訴CPU應執行何種運算。 也就是說,助憶符號所構成的組合語言是爲人設計的,而機器語言是對PC設計的。

  現在,我們再來剖析一個可以將所有ASCII碼顯示出來的程式。
  1. 輸入 DEBUG
  2. 輸入 A100
  3.輸入 MOV CX,0100 ;裝入迴圈次數
  MOV DL,00 ;裝入第一個ASCII碼,隨後每次迴圈裝入新碼
  MOV AH,02
  INT 21
  INC DL ;INC:遞增指令,每次將資料寄存器 DL 內的數值加 1
  LOOP 0105 ;LOOP:迴圈指令,每執行一次LOOP,CX值減1,並跳
  ;到迴圈的起始地址105,直到CX爲0,迴圈停止
  INT 20
  4.輸入 G即可顯示所有ASCII碼
 
  當我們想任意顯示字串,如:UNDERSTAND?,則可以使用DOS21H號中斷9H號功能。輸入下行程式,存檔並執行看看:
  1.輸入 A100
   MOV DX,109 ;DS:DX = 字串的起始位址
   MOV AH,9 ;DOS的09h功能調用
  INT 21 ;字串輸出
  INT 20
  DB 'UNDERSTAND?$';定義字串

  在組合語言中,有兩種不同的指令:1.正規指令:如 MOV 等,是屬於CPU的指令,用來告訴CPU在程式執行時應做些什麽,所以它會以運算碼(OP-code)的方式存入記憶體中;2.虛擬指令:如DB等,是屬於 DEBUG等編譯器的指令,用來告訴編譯器在編譯時應做些什麽。DB(Define Byte)指令用來告訴DEBUG 將單引號內的所有ASCII 碼放入記憶體中。使用 9H 功能的字串必須以$結尾。用D命令可用來查看DB虛擬指令將那些內容放入記憶體。
  6.輸入 D100
  1975:0100 BA 09 01 B4 09 CD 21 CD-20 75 6E 64 65 72 73 74 ......!. underst
  1975:0110 61 6E 64 24 8B 46 F8 89-45 04 8B 46 34 00 64 19 and$.F..E..F4.d.
  1975:0120 89 45 02 33 C0 5E 5F C9-C3 00 C8 04 00 00 57 56 .E.3.^_.......WV
  1975:0130 6B F8 0E 81 C7 FE 53 8B-DF 8B C2 E8 32 FE 0B C0 k.....S.....2...
  1975:0140 74 05 33 C0 99 EB 17 8B-45 0C E8 D4 97 8B F0 89 t.3.....E.......
  1975:0150 56 FE 0B D0 74 EC 8B 45-08 03 C6 8B 56 FE 5E 5F V...t..E....V.^_
  1975:0160 C9 C3 C8 02 00 00 6B D8-0E 81 C3 FE 53 89 5E FE ......k.....S.^.
  1975:0170 8B C2 E8 FB FD 0B C0 75-09 8B 5E FE 8B 47 0C E8 .......u..^..G..

  現在,我們來剖析另一個程式:由鍵盤輸入任意字串,然後顯示出來。db 20指示DEBUG保留20h個未用的記憶體空間供緩衝區使用。
  輸入A100
   MOV DX,0116 ;DS:DX = 緩衝區地址,由DB虛擬指令確定緩衝區地址
  MOV AH,0A ;0Ah 號功能調用
  INT 21 ;鍵盤輸入緩衝區
  MOV DL,0A ;由於功能Ah在每個字串最後加一個歸位元碼(0Dh由 Enter
  MOV AH,02 ;産生),使游標自動回到輸入行的最前端,爲了使新輸出的
  INT 21 ;字串不會蓋掉原來輸入的字串,所以利用功能2h加一
  ;個換行碼(OAh),使得游標移到下一行的的最前端。
  MOV DX,0118 ;裝入字串的起始位置
  MOV AH,09 ;9h功能遇到$符號才會停止輸出,故字串最後必須加上
  INT 21 ;$,否則9h功能會繼續將記憶體中的無用資料胡亂顯示出來
  INT 20
  DB 20 ;定義緩衝區
  送你一句話:學組合語言切忌心浮氣燥。

  客套話就不講了。工欲善其事,必先利其器。與其說DEBUG 是編譯器,倒不如說它是“直譯器”,DEBUG的A命令只可將一行組合語言指令轉成機器語言,且立刻執行。真正編譯器(MASM)的運作是利用文本編輯器 (EDIT等)將組合語言指令建成一個獨立且附加名爲.ASM的文字檔案,稱根源程式。它是MASM 程式的輸入部分。MASM將輸入的ASM文件,編譯成.OBJ文件,稱爲目的程式。OBJ文件僅包含有關程式各部份要載入何處及如何與其他程式合併的信 息,無法直接載入記憶體執行。鏈結程式LINK則可將OBJ文件轉換成可載入記憶體執行(EXEcute)的EXE文件。還可以用EXE2BIN,將符合條件 的EXE文件轉成COM文件(COM 文件不但佔用的記憶體最少,而且運行速度最快)。
  下面我們用MASM寫一個與用DEBUG寫的第一個程式功能一樣的程式。
  用EDIT編輯一個SMILE.ASM的根源程式文件。
  根源程式 DEBUG 程式
  prognam segment
  assume cs:prognam
  org 100h A100
  mov dl,1 mov dl,1
  mov ah,2 mov ah,2
  int 21h int 21
  int 20h int 20
  prognam ends
  end

  比較一下:1.因爲MASM會將所有的數值假設爲十進位,而DEBUG則只使用十六進位,所以在根源程式中,我們 必須在有關數位後加上代表進制的字母,如H代表十六進位,D代表十進位。若是以字母開頭的十六進位數位,還必須在字母前加個0,以表示它是數,如0AH。 2.根源程式增加五行敍述:prognam segment 與 prognam ends 是成對的,用來告訴 MASM 及LINK,此程式將放在一個稱爲PROGNAM(PROGram NAMe)的程式段內,其中段名(PROGNAM)可以任取,但其位置必須固定。assume cs:prognam 必須在程式的開頭,用來告訴編譯器此程式所在段的位置放在CS寄存器中。end用來告訴MASM,程式到此結束, ORG 100H作用相當於DEBUG的A100,從偏移量100開始組合語言。COM 文件的所有根源程式都必須包含這五行,且必須依相同的次序及位置出現,這點東西記下就行,千篇一律。接著,我們用MASM編譯SMILE.ASM。
  輸入 MASM SMILE ←不用打入附加名.ASM。
  Microsoft (R) Macro Assembler Version 5.10
  Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
  Object filename [SMILE.OBJ]: ←是否改動輸出OBJ檔案名,如不改就ENTER
  Source listing [NUL.LST]: ← 是否需要列表文件(LST),不需要就ENTER
  Cross-reference [NUL.CRF]: ←是否需要對照文件(CRF),不需要則ENTER
  50162 + 403867 Bytes symbol space free
  0 Warning Errors ←警告錯誤,表示編譯器對某些語句不理解,通常是輸入錯誤。
  0 Severe Errors ←嚴重錯誤,會造成程式無法執行,通常是語法結構錯誤。

  如果沒有一個錯誤存在,即可生成OBJ文件。OBJ中包含的是編譯後的二進位結果,它還無法被 DOS載入記憶體中加以執行,必須加以鏈結(Linking)。以LINK將OBJ文件(SMILE.OBJ)鏈結成 EXE 文件(SMILE.EXE)時,。
  1.輸入 LINK SMILE ←不用附加名OBJ
  Microsoft (R) Overlay Linker Version 3.64
  Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
  Run File [SMILE.EXE]: ← 是否改動輸出EXE檔案名,如不改就ENTER
  List File [NUL.MAP]: ← 是否需要列表文件(MAP),不需要則ENTER
  Libraries [.LIB]: ←是否需要庫文件,要就鍵入檔案名,不要則ENTER
  LINK : warning L4021: no stack segment← 由於COM文件不使用堆疊段,所以錯誤資訊
  ←"no stack segment"並不影響程式正常執行

  至此已經生成EXE文件,我們還須使用EXE2BIN 將EXE文件(SMILE.EXE),轉換成COM文件(SMILE.COM)。輸入EXE2BIN SMILE産生 BIN 文件(SMILE.BIN)。其實 BIN 文件與 COM 文件是完全相同的,但由於DOS只認COM、EXE及BAT文件,所以BIN文件無法被正確執行,改名或直接輸入 EXE2BIN SMILE SMILE.COM即可。現在,磁片上應該有 SMILE.COM 文件了,你只要在提示符號C:>下,直接輸入檔案名稱 SMILE ,就可以執行這個程式了。

  你是否覺得用編譯器産生程式的方法,比 DEBUG 麻煩多了!以小程式而言,的確是如此,但對於較大的程式,你就會發現其優點了。我們再將ASCII程式以編譯器方式再做一次,看看有無差異。首先,用EDIT.COM建立 ASCII.ASM 文件。
  prognam segment ;定義段
  assume cs:prognam ;把上面定義段的段基址放入 CS
  mov cx,100h ; 裝入迴圈次數
  mov dl,0 ; 裝入第一個ASCII碼,隨後每次迴圈裝入新碼
  next: mov ah,2
   int 21h
   inc dl ;INC:遞增指令,每次將資料寄存器 DL 內的數值加 1
  loop next ; 迴圈指令,執行一次,CX減1,直到CX爲0,迴圈停止
  int 20h
   prognam ends ;段終止
  end ;組合語言終止
  在組合語言的根源程式中,每一個程式列都包含三項元素:
    start: mov dl,1 ;裝入第一個ASCII碼,隨後每次迴圈裝入新碼
    識別字 運算式 注解

  在原始文件中加上注解可使程式更易理解,便於以後參考。每行注解以“;”與程式列分離。編譯器對注解不予理會, 注解的資料不會出現在OBJ、EXE或COM文件中。由於我們在寫根源程式時,並不知道每一程式列的地址,所以必須以符號名稱來代表相對地址,稱爲“標識 符”。我們通常在適當行的適當位置上,鍵入識別字。識別字(label)最長可達31 個位元組,因此我們在程式中,儘量以簡潔的文字做爲識別字。現在,你可以將此ASCII.ASM 文件編譯成 ASCII.COM 了。1.MASM ASCII,2.LINK ASCII,3.EXE2BIN ASCII ASCII.COM。

  注意:當你以編譯器組合語言你設計的程式時,常會發生打字錯誤、識別字名稱拼錯、十六進位數少了h、邏輯錯誤等。彙 編老手常給新人的忠告是:最好料到自己所寫的程式一定會有些錯誤(別人告訴我的);如果第一次執行程式後,就得到期望的結果,你最好還是在檢查一遍,因爲 它可能是錯的。原則上,只要大體的邏輯架構正確,查找程式中錯誤的過程,與寫程式本身相比甚至更有意思。寫大程式時,最好能分成許多模組,如此可使程式本 身的目的較單純,易於撰寫與查錯,另外也可讓程式中不同部份之間的界限較清楚,節省編譯的時間。如果讀程式有讀不懂的地方最好用紙筆記下有關寄存器、記憶體 等內容,在紙上慢慢比劃,就豁然開朗了。   下面我們將寫一個能從鍵盤取得一個十進位的數值,並將其轉換成十六進位數值而顯示於螢幕上的“大程式”。前言:要讓8086執行這樣的功能,我們必須 先將此問題分解成一連串的步驟,稱爲程式規劃。首先,以流程圖的方式,來確保整個程式在邏輯上沒有問題(不用說了吧!什麽語言都要有此步驟)。這種模組化 的規劃方式,稱之爲“由上而下的程式規劃”。而在真正寫程式時,卻是從最小的單位模組(副程式)開始,當每個模組都完成之後,再合併成大程式;這種大處著 眼,小處著手的方式稱爲“由下而上的程式設計”。

  我們的第一個模組是BINIHEX,其主要用途是從8086的BX寄存器中取出二進位數字,並以十六進位方式顯示在螢幕上。注意:副程式如不能獨立運行,實屬正常。
   binihex segment
   assume cs:binihex
  mov ch,4 ;記錄轉換後的十六進位位元數(四位)
  rotate: mov cl,4 ;利用CL當計數器,記錄寄存器數位移動次數
  rol bx,cl ;迴圈寄存器BX的內容,以便依序處理4個十六進位數
  mov al,bl ;把bx低八位元bl內資料轉移至al
  and al,0fh ;把無用位清零
  add al,30h ;把AL內資料加30H,並存入al
  cmp al,3ah ;與3ah比較
  jl printit ;小於3ah則轉移
  add al,7h ;把AL內資料加30H,並存入al
  printit:mov dl,al ;把ASCII碼裝入DL
  mov ah,2
   int 21h
   dec ch ;ch減一,減到零時,零標誌置1
  jnz rotate ;JNZ:當零標誌未置1,則跳到指定位址。即:不等,則轉移
  int 20h ;從副程式退回主程序
  binihex ends
   end

  利用迴圈左移指令ROL迴圈寄存器BX(BX內容將由第二個副程式提供)的內容,以便依序處理4個十六進位數: 1. 利用CL當計數器,記錄寄存器移位元的次數。2.將BX的第一個十六進位值移到最右邊。利用 AND (邏輯“與”運算:對應位都爲1時,其結果爲1,其餘情況爲零)把不要的部份清零,得到結果:先將BL值存入AL中,再利用AND以0Fh (00001111)將AL的左邊四位清零。由於0到9的ASCII碼爲30h到39h,而A到F之ASCII碼爲41h到46h,間斷了7h,所以得到 結果:若AL之內容小於3Ah,則AL值只加30h,否則AL再加7h。ADD指令會將兩個運算式相加,其結果存於左邊運算式內。標誌寄存器(Flag Register)是一個單獨的十六位寄存器,有9個標誌位元,某些組合語言指令(大部份是涉及比較、算術或邏輯運算的指令)執行時,會將相關標誌位置1或清 0, 常碰到的標誌位元有零標誌(ZF)、符號標誌(SF)、溢出標誌(OF)和進位元標誌(CF)。 標誌位元保存了某個指令執行後對它的影響,可用其他相關指令,查出標誌的狀態,根據狀態産生動作。CMP指令很像減法,是將兩個運算式的值相減,但寄存器或 記憶體的內容並未改變,只是相對的標誌位元發生改變而已:若 AL 值小於 3Ah,則正負號標誌位元會置0,反之則置1。 JL指令可解釋爲:小於就轉移到指定位置,大於、等於則向下執行。CMP和JG 、JL等條件轉移指令一起使用,可以形成程式的分支結構,是寫組合語言程式常用技巧。

  第二個模組DECIBIN 用來接收鍵盤打入的十進位數字,並將它轉換成二進位數字放於BX 寄存器中,供模組1 BINIHEX使用。
  decibin segment
  assume cs:decibin
  mov bx,0 ;BX清零
  newchar:mov ah,1 ;
  int 21h ;讀一個鍵盤輸入符號入al,並顯示
  sub al,30h ;al減去30H,結果存於al中,完成ASCII碼轉二進位碼
  jl exit ;小於零則轉移
  cmp al,9d
   jg exit ;左>右則轉移
  cbw ;8位al轉換成16位ax
  xchg ax,bx ;互換ax和bx內資料
  mov cx,10d ;十進位數字10入cx
  mul cx ;運算式的值與ax內容相乘,並將結果存於ax
  xchg ax,bx
   add bx,ax
   jmp newchar ;無條件轉移
  exit: int 20 ;回主程序
  decibin ends
   end
  CBW 實際結果是:若AL中的值爲正,則AH填入00h;反之,則AH填入FFh。XCHG常用於需要暫時保留某個寄存器中的內容時。
  當然,還得一個子程式(CRLF)使後顯示的十六進位數不會蓋掉先輸入的十進位數字。
  crlf segment
  assume cs:crlf
  mov dl,0dh ;回車的ASCII碼0DH入DL
  mov ah,2
   int 21h
   mov dl,0ah ;換行的ASSII碼0AH入AH
  mov ah,2
   int 21h
   int 20 ;回主程序
  crlf ends
  end

  現在我們就可以將BINIHEX、DECIBIN及CRLF等模組合併成一個大程式了。首先,我們要將這三個模組副程式略加改動。然後,再寫一段程式來調用每一個子程式。
  crlf proc near;
  mov dl,0dh
  mov ah,2
  int 21h
  mov dl,0ah
  mov ah,2
  int 21h
  ret
  crlf endp

  類似SEGMENT與ENDS的虛擬指令,PROC與ENDP也是成對出現,用來識別並定義一個程式。其實, PROC 真正的作用只是告訴編譯器:所調用的程式是屬於近程(NEAR)或遠端(FAR)。 一般的程式是由 DEBUG 直接調用的,所以用 INT 20 返回,用 CALL 指令所調用的程式則改用返回指令RET,RET會把控制權轉移到棧頂所指的位址,而該位址是由調用此程式的 CALL指令所放入的。
  各模組都搞定了,然後我們把副程式組合起來就大功告成
  decihex segment ;主程序
  assume cs:decihex
  org 100h
  mov cx,4 ;迴圈次數入cx;由於副程式要用到cx,故副程式要將cx入棧
  repeat: call decibin;調用十進位轉二進位副程式
  call crlf ;調用添加回、換行符副程式
  call binihex ;調用二進位轉十六進位並顯示副程式
  call crlf
  loop repeat ;迴圈4次,可連續運算4次
  mov ah,4ch ; 調用DOS21號中斷4c號功能,退出程式,作用跟INT 20H
  int 21H ; 一樣,但適用面更廣,INT20H退不出時,試一下它
  decibin proc near push cx ;將cx壓入堆疊,;
  ┇ exit: pop cx ;將cx還原; retdecibin endp binihex proc near push cx
  ┇ pop cx retbinihex endp crlf proc near
   push cx
  ┇ pop cx retcrlf endpdecihex ends end

  CALL指令用來調用副程式,並將控制權轉移到副程式位址,同時將CALL的下行一指令位址定爲返回位址,並壓 入堆疊中。CALL 可分爲近程(NEAR)及遠端(FAR)兩種:1.NEAR:IP的內容被壓入堆疊中,用於程式與程式在同一段中。2.FAR:CS 、IP寄存器的內容依次壓入堆疊中,用於程式與程式在不同段中。PUSH、POP又是一對指令用於將寄存器內容壓入、彈出,用來保護寄存器資料,副程式調 用中運用較多。堆疊指標有個“後進先出”原則,像PUSH AX,PUSH BX…POP BX,POP AX這樣才能作到保護資料絲毫不差。

  組合語言超濃縮教程到這要告一段落了,希望能奠定你獨立設計的基礎。而更多更好的技巧則全依賴你平時的積累了。祝你成功!