x86匯編語言復習筆記


0 寫在前面

  為了更深入的了解程序的實現原理,近期我學習了IBM-PC相關原理,並手工編寫了一些x86匯編程序

  在2017年的計算機組成原理中,曾對MIPS體系結構及其匯編語言有過一定的了解,考慮到x86體系結構在目前的廣泛應用,我通過兩個月左右的時間對x86的相關內容進行了學習。

  在《x86匯編語言實踐》系列中(包括本篇、x86匯編語言實踐(1)x86匯編語言實踐(2)x86匯編語言實踐(3)以及x86匯編語言實踐(4)),我通過幾個具體案例對x86匯編語言進行實踐操作,並記錄了自己再編寫匯編代碼中遇到的困難和心得體會,與各位學習x86匯編的朋友共同分享。

  我將我編寫的一些匯編代碼放到了github上,感興趣的朋友可以點擊屏幕左上角的小貓咪進入我的github,或請點擊這里下載源代碼。

  這是《x86匯編語言實踐》系列最后一篇文章,明天就要迎來x86匯編的期末考試了,希望所有朋友們以及先先能夠考試順利!

1 基礎知識

1.Intel 8086/8088PC機的CPU字長為16位,16位的信息稱為1個字,內存的基本單元為1個字節,但任何相鄰兩個單元都可以組成1個字。Intel 8086/8088PC機共有20根地址線,其尋址范圍為00000H~FFFFFH

2.用於間接尋址的寄存器有BX,SP,BP,SI,DI,其中,BX一般用於存放基址;在采用基址變址尋址時,采用SI或BX或DI寄存器,基址尋址默認的段是DS段(DS:[SI]);采用BP或SP寄存器,基址尋址默認的段是SS段(堆棧的位置和大小是由SP和SS共同決定的)。

3.串操作指令如MOVSB,STOSB,LODSB,SCASB,CMPSB,MOVSW,LODSW,STOSW,SCASW,CMPSW等,源操作數對應的地址是DS:[SI]目的操作數對應的地址是ES:[DI]

4.Intel8086/8088CPU共有9個1為的標志寄存器(標志位),為了便於CPU的加工,他們被組合在一起形成一個16位的程序狀態字寄存器PSW中。幾個比較重要的標志位有:

  • ZF:當運算結果為0時,ZF=1,否則ZF=0
  • SF:運算結果為負時,SF=1,否則SF=0
  • CF:算術運算最高位產生進位,CF=1.否則CF=0;還用於移位指令保存最高位左移或最低位右移移出的代碼。
  • DF:DF=1時每次串操作SI和DI減1,DF=0時每次串操作SI和DI加1。使用CLD可以將DF清零,即規定為正向操作字符串。
  • TF:TF=1時執行完一條產生單步中斷,中斷處理程序將TF置0。TF標志用於調試。
  • PF,AF,IF,OF這里我斗膽預測一啵,不考(因為真的沒有使用過)。

5.STD是將DF置1的指令,與CLD將DF清零的效果相反。使用STD,串指令對應的DI,SI寄存器每次操作后根據是SB還是SW操作自動減少1或2

6.邏輯地址向物理地址的轉化(書上P24)。地址轉化的動機:20位物理地址無法直接在16位字長的機器中直接運算,因此可以采用Intel的分段方法將其划分為16位段地址和16位段內地址(也稱為偏移地址)邏輯地址的基本形式為0000H:0000H,該邏輯地址表示物理地址的00000H。

 那么邏輯地址向物理地址的轉換方式可以表述為以下公式:段地址x10H + 偏移地址 = 物理地址

 舉例說明:邏輯地址1234H:5678H轉換為物理地址為:1234Hx10H + 5678H = 12340H + 5678H = 179B8H。再如:1234H:2001H = 12340H + 2001H = 14341H

7.幾個重要的數據傳送指令PUSH,POP,PUSHF,POPF

  • PUSH SRC:先SP = SP - 2 再 SS:[SP] <- SRC 
  • PUSHF    :先SP = SP - 2 再 SS:[SP] <- PSW
  • POP   SRC:先SRC <- SS:[SP] 再 SP = SP + 2
  • POPF         :先PSW <- SS:[SP] 再SP = SP + 2 

 總結而言,PUSH和POP是將操作數壓(彈)棧,POPF和PUSHF是將PSW標志寄存器壓(彈)棧

2 尋址方式

2-1 六種與數據有關的尋址方式

2-1-1 立即尋址

  直接將立即數寫到指令中的尋址方式。注意不得超出寄存器的字節范圍:AL8位,AX16位。

  【例】

  • AND AX,0FFFEH
  • MOV AL,100H
  • MOV AL,00000101B
  • MOV AX,512

  【不能使用】

  • MOV AL,100H
  • MOV AX,10000H (超出了字節范圍)

2-1-2 寄存器尋址

  使用寄存器的尋址方式。可以顯示使用,也可以隱式使用。也可以使用段寄存器CS,DS,SS,ES。

  【例】

  • MOV DS,AX 
  • PUSH DS
  • PUSHF (隱式操作PSW)
  • STD (隱式操作PSW)
  • CMC  (對CF取反操作,隱式操作PSW)

2-1-3 直接尋址

  直接使用操作數的偏移地址進行尋址的方式,偏移地址用[立即數]的形式表示,或者直接用數據段中定義的變量名表示,或用數據段中定義的變量名+立即數的形式表示。

  【例】

  • AND AX,[0FFFEH]
  • MOV AX,X    ;其中X為數據段中定義好的數據
  • MOV AX,STR+1   ;其中STR為數據段中定義好的數據,STR+1直接尋址到STR下一個字節單元的內容

2-1-4 寄存器間接尋址

  使用寄存器中存儲的偏移地址進行尋址。注意只能使用尋址寄存器BX,BP,SI,DI進行尋址,而不能用DX等進行尋址。此外,尋址的地址必須為16位,即不能使用BL等進行尋址。

  【例】

  • MOV AX.[BX]
  • MOV BH,[BP]
  • MOV CX,[SI]
  • MOV DL,[DI]

  以上四條指令等價於

  • MOV AX.DS:[BX]
  • MOV BH,SS:[BP]
  • MOV CX,DS:[SI]
  • MOV DL,ES:[DI]

  但是在每條指令前加上一個段超越的段名,既麻煩又沒必要,因此通常都默認缺省為上述隱含段規則。

  【不能使用】

  • MOV AX,[DX]  (不能用DX)
  • MOV DL,[BL]  (必須為16位尋址)

2-1-5 寄存器相對尋址

  在寄存器間接尋址的基礎上,再增加一個常偏移量。形式多變,大致有如下幾種

  【例】

  • MOV AX,[BX+100]
  • MOV AX,[SI+10H]   <==>  MOV AX,10H[SI]
  • MOV AX,ARRAY[SI]
  • MOV TABLE[DI],AL
  • MOV TABLE[DI+1],AL  3~5展示了立即數也可以是數據段中定義好的變量名

  最終在debug下所有的尋址有效地址會被計算成[DI+XXXX]的形式,XXXX是一個十六進制數。

2-1-6 基址變址尋址

  即基址加變址尋址方式,基址采用BX,BP尋址,變址采用DI,SI尋址,尋址規則相對固定。

  【例】

  • MOV AX,[BX][SI]
  • MOV AX,[BX+SI]
  • MOV ES:[BX+SI],AL
  • MOV [BP+DI],AX   
  • MOV AX,[BX+SI+200]
  • MOV ARRAY[BP+SI],AX

  其中,段取決於基址寄存器,如BX的段就默認為DS;BP缺省為SS。當然,有指定段的情況除外。也可以在兩個寄存器加和的基礎上再增加一個立即數。

  【不能使用】

  • MOV [BX+CX],AX  (CX不能做變址寄存器)
  • MOV [BX+BP],AX  (BP不能做變址寄存器)
  • MOV [BX+DI],ARRAY  (兩個全在內存中的操作數,不符合語法)

2-1 五種與轉移地址有關的尋址方式

2-2-1 標號與過程名

  與轉移地址相關的指令主要是JMP和CALL指令,而要讓代碼能夠跳躍執行到指定的IP處則需要通過標號指示某行代碼,或是通過過程名定義進行CALL調用。

2-2-2 段內直接尋址

  即直接使用標號與過程名進行跳轉。根據位移量的不同,可以加SHORT(8BITS)和NEAR PTR(16BITS)操作符。其中,條件跳轉只能是8位因此省略SHORT,而JMP則缺省為16位位移量。因此,在跳轉位移已知的前提下,使用JMP SHORT可以提高程序的執行效率。

  【例】

  • JMP L1
  • CALL P1
  • JMP SHORT L1
  • JMP NEAR PTR L1  (L!與當前IP位移量為16位的數值)

2-2-3 段內間接尋址

  即將轉移目的地址放入寄存器中進行存儲,調用的也是寄存器中的相應數值。

  【例】

  • MOV AX,OFFSET P1     CALL AX
  • JMP BX

  這里要特別注意段內間接尋址與數據尋址中寄存器間接尋址的區別,后者有[]進行尋址。

  • MOV AX,OFFSET P1    MOV ADD1,AX    CALL ADD1
  • MOV BX,OFFSET ADD1   CALL [BX]

  以上兩種也是段內間接尋址,注意這里ADD1不是過程名,而是數據段中的一個數據的地址,存放了子程序P1的位移量。BX則存放了ADD1的地址,因此調用CALL [BX]也屬於段內間接尋址。

2-2-4 段間直接尋址

  具備FAR屬性的尋址。例如P2為一個有FAR屬性定義的過程:

  • CALL FAR P2

2-2-5 段間間接尋址

   形式如下:

  • JMP DWORD PTR [BX+INTERS]

  只要DWORD PTR后面是除了立即尋址寄存器尋址之外的任何一種數據尋址方式即可。

3 語法知識

【判斷指令正誤】

  • MOV [CX],AL     不正確。CX不能作為寄存器間接尋址的寄存器
  • MOV BH,320     不正確。320超出了8位范圍(255)
  • MOV DS,2000H     不正確。不存在從立即數到段寄存器的數據通路。此外,段寄存器作目的操作數時,不允許使用CS作為目的操作數。
  • ADD SI,FDDH     不能確定。如果在數據段定義過一個名為FDDH的數據變量,且該數據在字節范圍內,則此指令正確。否則會認為FDDH是一個未定義的變量,改成0FFDH后正確。
  • SHL AX,2     不正確。移位指令格式中,移位的數量count只能是1或CL。移動位數大於1(0和1也可以)必須放入CL寄存器中操作。
  • CMP BYTE PTR [SI],X     不正確。源操作數和目的操作數不能同時為內存中的數。
  • LEA BX,[SI]       正確。
  • LDS BX,[DX]     不正確。DX不能用作寄存器間接尋址的寄存器
  • JMP BYTE PTR AX     不正確。轉移只有NEAR/FAR PTR + 標號或SHORT+標號或只有標號/寄存器的形式。沒有JMP BYTE PTR的形式
  • JMP AX     正確。
  • JMP [AX]     不正確。AX不能用作間接尋址的寄存器。
  • RET 5     不正確。后面的立即數必須為偶數。這是為了帶參數調用的子程序在返回時要彈出幾個參數的位置,進而維持堆棧的平衡。
  • MOV [BX+SI+10],100     不正確。注意只有在寄存器相對尋址取數(作為源操作數)時直接尋址即可,若作為目的操作數則必須指定size。修改為MOV BYTE PTR[BX+SI+10],100即可。
  • DIV AL    正確。執行結果為AX=0001即除以本身,商1余0。

4 簡答問題

4-1 解讀指令執行過程

 1. RET EXP

  IP ← [SP]

  SP ← SP + 2

  SP ← SP + EXP

2.RETF

  注意如果是FAR屬性的過程,返回時是段間返回(即在匯編器中會被匯編成RETF指令),會執行以下過程

  IP ← [SP]

  SP ← SP + 2

  CS ← [SP]

  SP ← SP + 2

3.PUSH SRC

  SP ← SP - 2

  SS:[SP] ← SRC

4.PUSHF

  SP ← SP - 2

  SS:[SP] ← PSW

5.POP DST

  DST ← SS:[SP]

  SP ← SP + 2

6.POPF

  PSW ← SS:[SP]

  SP ← SP + 2

7.LEA REG,SRC

  將SRC的偏移地址送入REG  

8.LDS/LES REG,SRC

  將SRC中的雙字內容分別送REG和DS/ES中。

  這里的SRC中的雙字通常保存的是某個程序或變量的邏輯地址(SEG:OFFSET),前面的低字送入REG,后面的高字送入DS/ES。

9.CALL FAR PTR P1 

  SP ← SP - 2

  SS:[SP] ← 返回地址段值

  SP ← SP - 2

  SS:[SP] ← 返回地址偏移值

  IP  ← 目的偏移地址

  CS ← 目的段地址  

10.CALL AX(假設為段內間接調用)

  SP ← SP - 2

  SS:[SP] ← 返回地址偏移值

  IP ← AX中有效地址

11.JMP [BX]

  從內存中根據BX間接尋址,取得的標號值送IP進行跳轉。

12.JMP DX

  將DX中有效地址偏移值送入IP進行跳轉。

13.CALL DWORD PTR [BX]

  段間間接調用,過程地址CS:IP(這是一個雙字,因此用DWORD PTR)位於數據段中通過BX間接尋址得到。

14.LOOP LP1

  CX = CX - 1

  若CX ≠ 0,則跳轉至LP1,否則順序執行之后代碼。

15.CMP BX,X1

  分別通過寄存器尋址和直接尋址取得BX與X1的值,計算BX-X1並影響標志位。(如可用ZF判斷兩數是否相等、CF=1或SF=1則BX<X1等等,再配合JC,JS等指令即可進行條件跳轉

16.INT 21H / IRET

  Intel8086/8088指令系統中用於支持中斷調用的指令為INT n,返回中斷的指令時IRET。此外,CLI用於清除中斷標志,STI用於設置中斷標志。

                                               ----《書》P173

  INT 21H的執行過程:

  • SP ← SP - 2
  • SS:[SP] ← PSW
  • SP ← SP - 2
  • SS:[SP] ← INT N 下一條指令的CS
  • SP ← SP - 2
  • SS:[SP] ← INT N 下一條指令的IP
  • IP ← [0000 : N*4]
  • CS ← [0000 : N*4+2]

  IRET的執行過程:

  • IP ← SS:[SP]
  • SP ← SP + 2
  • CS ← SS:[SP]
  • SP ← SP + 2
  • PSW ← SS:[SP]
  • SP ← SP + 2

4-2 圖解移位指令

4-3 指出目的寄存器中的內容

  已知:DS = 2100H,BX = 0100H,SI = 0002H;內存中:[21100H] = 12H,[21101H] = 34H,[21102H] = 56H,[21103H] = 78H。

  • MOV AX,[101H]  ;直接尋址,默認段DS。AX的結果為3456H ?在DOS下,匯編后變成了MOV AX,101這種立即尋址形式,使得AX最終的結果為0101H
  • MOV AX,WORD PTR [BX+2] ;寄存器相對尋址。AX結果為7856H
  • MOV AL,BYTE PTR [BX][SI+1] ;基址變址尋址。AL結果為78H
  • MOV AX,100H [SI] ;寄存器相對尋址。AX結果為7856H(注意取出的為一個字!而且是小端存儲!低字節在高位!

4-4 指出CS與IP的值

   已知:DS = 2100H,BX = 0101H,CS = 1900H;內存中:[21101H] = 0C7H,[21102H] = 0FFH,[21103H] = 00H,[21104H] = 0F0H。

  • JMP BX  ;CS = 1900H,IP=0101H
  • JMP [BX]        ;CS = 1900H,IP=0FFC7H           
  • JMP WORD PTR [BX+1]          ;CS = 1900H,IP=00FFH   (注意小端存儲,低字節在低地址)
  • JMP DWORD PTR [BX]            ;CS = 0F000H,IP=0FFC7H  (取雙字分別取得的是段地址與偏移值)

4-5 根據要求畫內存示意圖

  1.定義MYSEG數據段,其中有S1,內容'ABCD'以00H結尾;S2是能用AH=9,INT 21H顯示的字符串;S3為10x10的二維字數組。L1為S1+S2+S3的長度。

  則可以畫出該數據段內存示意圖如下:

  

 

  其中一個英文字母占據1字節,即一個內存單元;顯示字符串必須以'$'結尾。常量定義形式應為 L1 EQU $-S1。注意,字數組一個字占兩個字節。故L1的值為210

  2.書P96第2題的數據段可以定義如下:

1 DATA SEGMENT PARA
2     X1 DB 'Display string',0DH,0AH,'$'
3     X2 DB 32
4     X3 DW 40H
5     X4 DD A000H,0120H
6     X5 DW 10 DUP(8 DUP(0))
7     X6 EQU $-X1
8 DATA ENDS

4-6 綜合練習題

  【題簽】有數據段定義如下:

1 DATA1 SEGMENT PARA
2     X1 DB 20H,?,'A'
3     X2 DW 2 DUP(1,2DUP(1,?))
4     X3 DD 12345678H
5     LEN EQU $-X2
6 DATA1 ENDS

  (1)畫出內存圖

  (2)執行MOV AX,X3+1后,AX為?

  (3)執行MOV CX,LEN后,CX為?

  【解】

  (1)內存圖如下:

  

  說明:對於字和雙字的定義,低字節在低位,因此如對於DW 1234H來說,在內存中由低地址到高地址依次為34H、12H;對於DD 12345678H而言,在內存中由低地址到高地址依次為78H、56H、34H、12H。而對於數組的定義而言,如DUP,則是按照其定義先后順序在內存中由低到高排列的。必須注意的是:由於前面定義的是字DW,所以DUP中的每一個數值都占據兩個內存單元,即1個字的空間,這在畫內存圖時必須要注意!

  (2)AX = 3456H這道題這里有點小bug,編譯后會報告1個warning,更好的改進是使用MOV AX,WORD PTR X3+1

    這道題可以改進成一個更有意思的考法MOV AX,WORD PTR X3+2

    這樣以來就要聯系(1)中畫的內存圖了。內存中高地址存放的是數據中的高字節。因此結果應該是AX=1234H

  (3)CX = 18H(可以表示為16進制,一定注意數組定義DW DUP的問題!這會對LEN的計算產生影響)

5 編程題

5-1 加法

  計算Z=X+Y。其中X,Y為16位數,Z為32位數。

1 XOR     DX,DX
2 MOV     AX,X
3 ADD     AX,Y
4 ADC     DX,0
5 MOV     WORD PTR Z+2,DX
6 MOV     WORD PTR Z,AX

  這里引入一個技巧:為了操作32位數,我們需要借用DX:AX進行操作,我們一個一個地計算這兩個寄存器中的數值,低位產生的進位補到DX中去,使用ADC指令。最后為內存中的Z使用WORD PTR進行賦值即可。

5-2 右移

  將32位X右移4位。

 1 MOV     AX,WORD PTR X
 2 MOV     DX,WORD PTR X+2
 3 SHR     DX,1
 4 RCR     AX,1
 5 SHR     DX,1
 6 RCR     AX,1
 7 SHR     DX,1
 8 RCR     AX,1
 9 SHR     DX,1
10 RCR     AX,1

  必須要注意的是這里必須使用SHR與RCR指令配合4次,每次移動1位進行使用,這是由於,CF只能存放1位數字

5-3 乘法

  用移位及加法指令,將32位數X計算X = X * 10。

 1 MOV     AX,WORD PTR X
 2 MOV     DX,WORD PTR X+2
 3 SHL     AX,1
 4 RCL     DX,1
 5 MOV     BX,AX
 6 MOV     CX,DX
 7 SHL     AX,1
 8 RCL     DX,1
 9 SHL     AX,1
10 RCL     DX,1
11 ADD     AX,BX
12 ADC     DX,CX
13 MOV     WORD PTR X,AX
14 MOV     WORD PTR X+2,DX

  這里用到的技巧是將X*10分解成X*2 + X*8來計算,也就是將X左移1位保存下來再左移2位加上剛才保存的值即可。

5-4 打印

  將內存中16位X顯示為十六進制ASCII碼。

 1     MOV     BX,X
 2     MOV     CX,4
 3 LP:
 4     PUSH     CX
 5     MOV     CL,4
 6     ROL     BX,CL
 7     MOV     AL,BL
 8     AND     AL,0FH
 9     ADD     AL,30H
10     CMP     AL,39H
11     JBE     DISP
12     ADD     AL,7
13 
14 DISP:
15     MOV     DL,AL
16     MOV     AH,2
17     INT     21H
18     POP     CX
19     LOOP     LP

  注意以下幾點技巧:

  • 16位數字X需要輸出4位數字,因此設置CX的值為4作為外層循環次數
  • 輸出每次對X的值進行循環左移4位(不帶CF)的ROL指令,這樣每次BL的低4位即為當前要輸出的值
  • 由於又用到了CL,因此外層循環的CX需要在第4行處壓棧處理
  • 由於每次輸出只有4位,而最少取出AL為8位,因此需要使用第8行AND AL,0FH來屏蔽AL的高4位
  • 需要注意的是,在16進制中超過9的數字變成了A,由於ASCII碼中‘9’與‘A’之間相差8,因此需要判斷是否需要給AL增加相應值,使用的是ADD AL,7實現
  • 輸出單個字符的中斷調用為2號中斷調用

6 寫在最后

  熊老師的《x86匯編語言》這門課程是本學期選的最成功的一門課程,熊老師對學生也十分認真負責。這也再次印證了那個真理,那就是只有實踐,才能真正把理論中的內容理解、消化。

  從最開始的連課都聽不懂、程序寫不出、編程毫無頭緒,到后來經歷了幾次作業的歷練后,思路漸漸清晰,我不得不十分感謝熊老師的嚴格要求。

  匯編是一種十分貼近計算機底層的語言,它深刻的揭示了程序運行的過程以及內存的使用和分配機制,在本科階段,有匯編編程的鍛煉經歷,我認為是十分有必要的。

  最后,在編寫這篇筆記的過程中,還要特別感謝小馬哥給我提出的寶貴的修改意見!

  明天就要期末考試了。真心的希望先先能夠發揮高水平,取得好成績。與各位共勉!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM