1. 程式人生 > >《80X86匯編語言程序設計教程》十一 32位代碼段和16位代碼段切換實例

《80X86匯編語言程序設計教程》十一 32位代碼段和16位代碼段切換實例

段描述符 緩沖 row 宏定義 純粹 強行 專用 訪問 錯誤

1、 演示32位代碼段與16位代碼段之間的切換。實現的功能是以十六進制和ASCII碼字符兩種形式顯示從內存地址100000H開始的16個字節的內容。

2、 源代碼如下:

  1 ;DosTest.Asm
  2 ;16位偏移的段間轉移指令的宏定義
  3 ;使用於16位段,用於跳轉到32位目的段
  4 ;註意:標號偏移必須在16位二進制符號數數能表示的範圍之內
  5 JUMP16    macro    selector,offsetv
  6         db    0eah                        ;操作碼
  7         dw    offsetv                     ;
16位偏移 8 dw selector ;段值或者選擇子 9 endm 10 11 ;32位偏移的段間轉移指令的宏定義 12 ;使用於32位段,用於跳轉到16位目的段 13 JUMP32 macro selector,offsetv 14 db 0eah ;操作碼 15 dw offsetv ;32位偏移 16 dw 0 17 dw selector ;
選擇子 18 endm 19 20 ;存儲段描述符結構類型的定義 21 DESCRIPTOR struc 22 LimitL dw 0 ;段界限(0~15) 23 BaseL dw 0 ;段基地址(0~15) 24 BaseM db 0 ;段基地址(16~23) 25 Attributes dw 0 ;段屬性 26 BaseH db 0
;段基地址(24~31) 27 DESCRIPTOR ends 28 29 30 ;偽描述符結構類型的定義 31 PDESC struct 32 Limit dw 0 ;16位界限 33 Base dd 0 ;基地址 34 PDESC ends 35 36 ;7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 37 ;G D 0 AVL Limit(19…16) P DPL DT TYPE 38 ;常量定義 39 ATDR = 0090h ;存在的只讀數據段屬性值(用於描述源數據段) 40 ATDW = 0092h ;存在的可讀寫數據段屬性值(用於描述目的數據段) 41 ATDWA = 0093h ;存在的已訪問可讀寫數據段屬性值 42 ATCE = 0098h ;存在的只執行16位代碼段屬性值 43 ATCE32 = 4098h ;存在的只執行32位代碼段屬性值 44 DATALEN = 16 ;源數據段長度 45 46 47 ;須使用386特權指令 48 .386P 49 50 ;----------------------------------- 51 ;數據段 52 dseg segment use16 ;16位段 53 ;GDT表 54 GDT label byte 55 DUMMY DESCRIPTOR<> ;空描述符 56 CODE32_SEL = 08h ;32位代碼段描述符選擇子 57 CODE32 DESCRIPTOR<CODE32LEN-1,,,ATCE32,> 58 CODE16_SEL = 10h ;16位代碼段描述符選擇子 59 CODE16 DESCRIPTOR<0ffffh,,,ATCE,> 60 DATAS_SEL = 18h ;源數據段描述符選擇子 61 DATAS DESCRIPTOR<DATALEN-1,,10h,ATDR,>;段基地址100000h 62 DATAD_SEL = 20h ;目的數據段描述符選擇子 63 DATAD DESCRIPTOR<DATALEN*8-1,80a0h,0bh,ATDW,0>;段基地址0b80a0h 64 STACKS_SEL = 28h ;堆棧段描述符選擇子 65 STACKS DESCRIPTOR<0ffffh,,,ATDWA,>;段基地址0000h,棧頂基址0ffffh 66 NORMAL_SEL = 30h ;規範段描述符選擇子 67 NORMAL DESCRIPTOR<0ffffh,0,0,ATDW,>;段基地址0000h,棧頂基址0ffffh 68 GDTLEN = $ - GDT ;GDT表長度 69 ; 70 VGDTR PDESC<GDTLEN-1,> ;GDT偽描述符 71 VARSS dw ? ;用於保存SS變量 72 dseg ends 73 74 ;----------------------------------- 75 ;實模式下代碼段 76 csegr segment use16 real 77 assume cs:csegr,ds:dseg 78 start: ;程序入口 79 mov ax,dseg 80 mov ds,ax 81 ; 82 mov bx,16 ;寫VGDTR(GDT首地址轉線性地址) 83 mul bx 84 add ax,offset GDT 85 adc dx,0 86 mov word ptr VGDTR.Base,ax 87 mov word ptr VGDTR.Base + 2,dx 88 ; 89 mov ax,cseg32 ;寫32位代碼段段基址 90 mul bx 91 mov CODE32.BaseL,ax 92 mov CODE32.BaseM,dl 93 mov CODE32.BaseH,dh 94 ; 95 mov ax,cseg16 ;寫16位代碼段段基址 96 mul bx 97 mov CODE16.BaseL,ax 98 mov CODE16.BaseM,dl 99 mov CODE16.BaseH,dh 100 ; 101 mov ax,ss ;寫堆棧段段基址 102 mul bx 103 mov STACKS.BaseL,ax 104 mov STACKS.BaseM,dl 105 mov STACKS.BaseH,dh 106 mov VARSS,ss ;保存實模式下段基址 107 ; 108 lgdt fword ptr VGDTR ;裝載VGDTR到GDTR 109 ; 110 cli ;關中斷 111 call ENABLEA20 ;開地址線A20 112 ; 113 mov eax,cr0 ;CR0的PE位置1 114 or eax,1 115 mov cr0,eax 116 ;進入32位代碼段 117 JUMP16 <CODE32_SEL>,<low offset SPM32>;切換到保護模式 118 ;此時已經回到實模式 119 TOREAL: 120 mov ax,dseg 121 mov ds,ax 122 mov ss,VARSS ;恢復實模式下的SS 123 call DISABLEA20 ;關閉地址線A20 124 sti ;開中斷 125 mov ah,07h ;等待按鍵終止程序 126 int 21h 127 mov ah,4ch 128 int 21h 129 130 131 ;打開地址線A20號 132 ENABLEA20 proc 133 push ax 134 in al,92h 135 or al,2 136 out 92h,al 137 pop ax 138 ret 139 ENABLEA20 endp 140 141 ;關閉地址線A20號 142 DISABLEA20 proc 143 push ax 144 in al,92h 145 and al,0fdh 146 out 92h,al 147 pop ax 148 ret 149 DISABLEA20 endp 150 151 csegr ends 152 153 ;----------------------------------- 154 ;32位代碼段 155 cseg32 segment use32 pm32 156 assume cs:cseg32 157 SPM32: 158 mov ax,STACKS_SEL 159 mov ss,ax ;裝載堆棧段描述符選擇子 160 mov ax,DATAS_SEL 161 mov ds,ax ;裝載源數據段描述符選擇子 162 mov ax,DATAD_SEL 163 mov es,ax ;裝載目的數據段描述符選擇子 164 ;以下開始以ASCII碼形式顯示源16個字節 165 ;目的數據段需要16 * (4 + 2) = 96個字節 166 xor esi,esi ;設置指針和計數器 167 xor edi,edi 168 mov ecx,DATALEN ;16個數據,循環16次 169 cld ;清方向標誌位 170 NEXT: 171 lodsb ;從源數據段裝載一個byte數據到al並移動指針 172 push   ax 173 call   TOASCII ;低4位轉ASCII碼(一個byte) 174 mov ah,7 ;顯示屬性為黑底白字(再一個byte) 175 shl eax,16 ;暫存在eax高16位 176 pop ax 177 shr al,4 ;高4位轉ASCII碼 178 call TOASCII 179 mov ah,7 180 stosd ;eax的4個byte(dword)存入目的數據段並移動指針 181 mov al, ;顯示空格,屬性為黑底白字,2個字節包含字符ASCII碼和字符屬性 182 stosw ;ax的2個byte(word)入目的數據段並移動指針 183 loop NEXT 184 ;變化到16位代碼段 185 JUMP32 <CODE16_SEL>,<offset SPM16> 186 ;jmp far ptr SPM16 ;這裏取代完全沒有問題 187 188 ;把AL低4位的十六進制數轉換成對應的ASCII碼,保存在AL中 189 TOASCII proc 190 and al,0fh 191 add al,90h 192 daa 193 adc al,40h 194 daa 195 ret 196 TOASCII endp 197 198 CODE32LEN = $ - SPM32 199 cseg32 ends 200 201 ;----------------------------------- 202 ;16位代碼段 203 204 cseg16 segment use16 pm16 205 assume cs:cseg16 206 SPM16: ;跳轉過來時ss、es的值都沒有改變,實際上還是在保護模式 207 ;以下開始以十六進制數形式顯示源16個字節 208 ;目的數據段需要16 * 2 = 32個字節 209 xor si,si ;源數據段指針歸位 210 mov di,DATALEN * 3 * 2 ;這個語句是多余的,這裏重新設置di沒有意義 211 mov ah,7 ;顯示屬性為黑底白字 212 mov cx,DATALEN 213 AGAIN: 214 lodsb ;從源數據段裝載一個byte數據到al並移動指針 215 stosw ;ax的2個byte(word)入目的數據段並移動指針 216 loop   AGAIN 217 ; 218 mov ax,NORMAL_SEL ;裝載規範段描述符選擇子到ds和es 219 mov ds,ax ;這將引起高速緩存寄存器的刷新 220 mov es,ax 221 ; 222 mov eax,cr0 ;切換到實模式下 223 and eax,0fffffffeh 224 mov cr0,eax 225 ;切換回實模式 226 jmp far ptr TOREAL 227 cseg16 ends 228 end start

3、 源代碼有幾處要說明的地方

  1) 原書中的“JUMP16 CODE32_SEL,<offset SPM32>”語句須改為“JUMP16 <CODE32_SEL>,<low offset SPM32> ”語句,原因是16位段不支持32位偏移(offset SPM32為32位立即數)

  2) 原書中的“CODE32LEN = $”需要修改為“CODE32LEN = $ - SPM32”,如果不是原書印刷等之類的錯誤,那麽絕對是作者邏輯錯誤

4、 運行效果與相關說明

  1) 使用DiskGenuis復制.exe目標文件到DOS虛擬系統

  2) 打開虛擬機進入DOS7.1,使用“cls”指令清屏(否則將影響輸出的視覺效果)

  3) 執行目標程序,將看到輸出結果

技術分享圖片

  4) 使用adu.exe驗證一下輸出是正確的

技術分享圖片

5、 實現步驟的簡單闡述

  1) 作切換到保護方式的準備

    2個16位數據段描述符、1個16位代碼段描述符、1個16位堆棧段描述符、1個32位代碼段描述符和1個規範段描述符。這裏沒必要再說了,只是註意下,32位代碼段描述符在設置界限時采用的方法。另外,這個界限值不是長度,在以字節為粒度時是偏移量。

  2) 切換到保護方式一個32位代碼段

    在上一個實例中已經提到過,這個JUMP宏實際上就是一條特殊的遠跳轉指令,這裏要關註的一個東西是,跳轉時的地址偏移問題。我簡單說下:

    JUMP16用於16位段中,實現跳入32位段。由段間絕對跳轉的性質可知,最大偏移是0FFFFH,也就是說,JUMP16實現的從16位段到32位段的跳轉是有條件的:目標地址標號在32位段的段內偏移必須不大於0FFFFH

    JUMP32用於32位段中,實現跳入16位段。這個也只需要註意同一個地方,那就是16位段最大偏移為0FFFFH,所以跳轉時必須保證高16位為0,作者在這裏使用了宏定義把雙字類型拆分成兩個字類型的域,並把高字強行設置為0,對安全性有一定的提高

    從上面來看,跳轉時不需要關註段寄存器的內容,此外,16位段與32位段之間的相互跳轉與實模式還是保護模式沒有半點關系,從這裏也可以看到,實模式到保護模式的切換,在把VGDTR裝載到GDTR寄存器以及將CR0的PE位置1後,就是一個簡單的跳轉指令,所以,也支持在模式切換的同時進行段類型的切換。

    如果能保證足夠安全,可以完全不用作者的宏來實現這些跳轉,就像在本例中最後由保護模式切換為實模式的“jmp far ptr TOREAL”那樣,直接使用標號進行遠跳,也是一樣的,當然,這裏要註意一些問題,這個問題也正體現之前一直說的這個遠跳還是特殊的地方:這個宏中遠跳指令可以重置自己設定的代碼段值/代碼段選擇子和代碼段內偏移分別到CS、IP/EIP。在保護模式下,裝入段CS的是段選擇子而不是段基地址,使用該指令來跳轉是必須的,凡是裝入的是段值而不是段選擇子的情況,都可以使用遠跳指令取代

  3) 把源數據轉十六進制數碼的ASCII碼,並直接填入顯存

    采用的是直接寫屏的方式(參考“《80X86匯編語言程序設計教程》四 輸入輸出與中斷”)。顯存開始地址為0B8000H,這裏的0b80a0h表示在3號顯示方式下,屏幕第2行開頭的位置。

  4) 切換到16位段代碼

  5) 把源數據直接作為ASCII碼填入顯存

  6) 切回實模式

6、 特別說明

  1) 本例沒有建立專用堆棧,但是在原堆棧上建立了堆棧段,所以保護模式下可進行堆棧操作

  2) 同上個實例一樣,大量簡化處理,沒有IDT和LDT,DPL都設置為了0。

  3) 關於遠跳的特殊之處的說明,各個段寄存器所配的高速緩沖寄存器在實模式下依然起作用。實模式下要求它的內容應該如下表:

段基地址

段界限

(固定)

其它段屬性

G

R

W

E

CS

當前CS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

Y

-

N

SS

當前SS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

W

-

DS

當前DS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

ES

當前ES*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

FS

當前FS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

GS

當前GS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

    其中:“Y”表示“是”,“N”表示“否”,“B”表示字節,“U”表示向高擴展段,“W”表示字操作堆棧。由於實模式下不可以設置高速緩存寄存器(也就是說,即使改變段寄存器中的段值,也不會引起高速緩存寄存器中內容的刷新),所以我們必須在保護模式下提前刷新它們到符合要求的值,再切換回保護模式。源代碼中的“規範段描述符選擇子”做的就是這樣一個事情。我測試過,如果將它們去掉,程序在切換回實模式後將死機。此外,需要說明的是,源代碼中的JMP32完全沒必要用,這個純粹就是跳轉,根本不需要刷新高速緩存寄存器,這裏也沒有進行模式切換,我不知道作者放這是為了什麽,感覺容易誤導到讀者

《80X86匯編語言程序設計教程》十一 32位代碼段和16位代碼段切換實例