1; ==========================================
2; pmtest2.asm
3; 编译方法:nasm pmtest2.asm -o pmtest2.com
4; ==========================================
5
6%include "pm.inc" ; 常量, 宏, 以及一些说明
7
8org 0100h
9 jmp LABEL_BEGIN
10
11[SECTION .gdt]
12; GDT
13; 段基址, 段界限 , 属性
14LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
15LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; ***注意此处为Normal 描述符*****
16LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
17LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
18LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW ; Data
19LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
20LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
21LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
22; GDT 结束
23
24GdtLen equ $ - LABEL_GDT ; GDT长度
25GdtPtr dw GdtLen - 1 ; GDT界限
26 dd 0 ; GDT基地址
27
28; GDT 选择子
29SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
30SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
31SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
32SelectorData equ LABEL_DESC_DATA - LABEL_GDT
33SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
34SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
35SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
36; END of [SECTION .gdt]
37
38[SECTION .data1] ; 数据段
39ALIGN 32
40[BITS 32]
41LABEL_DATA:
42SPValueInRealMode dw 0
43; 字符串
44PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
45OffsetPMMessage equ PMMessage - $$
46StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
47OffsetStrTest equ StrTest - $$
48DataLen equ $ - LABEL_DATA
49; END of [SECTION .data1]
50
51
52; 全局堆栈段
53[SECTION .gs]
54ALIGN 32
55[BITS 32]
56LABEL_STACK:
57 times 512 db 0
58
59TopOfStack equ $ - LABEL_STACK - 1
60
61; END of [SECTION .gs]
62
63
64[SECTION .s16]
65[BITS 16]
66LABEL_BEGIN:
67 mov ax, cs
68 mov ds, ax
69 mov es, ax
70 mov ss, ax
71 mov sp, 0100h
72
73 mov [LABEL_GO_BACK_TO_REAL+3], ax
74 mov [SPValueInRealMode], sp
75
76 ; 初始化 16 位代码段描述符
77 mov ax, cs
78 movzx eax, ax
79 shl eax, 4
80 add eax, LABEL_SEG_CODE16
81 mov word [LABEL_DESC_CODE16 + 2], ax
82 shr eax, 16
83 mov byte [LABEL_DESC_CODE16 + 4], al
84 mov byte [LABEL_DESC_CODE16 + 7], ah
85
86 ; 初始化 32 位代码段描述符
87 xor eax, eax
88 mov ax, cs
89 shl eax, 4
90 add eax, LABEL_SEG_CODE32
91 mov word [LABEL_DESC_CODE32 + 2], ax
92 shr eax, 16
93 mov byte [LABEL_DESC_CODE32 + 4], al
94 mov byte [LABEL_DESC_CODE32 + 7], ah
95
96 ; 初始化数据段描述符
97 xor eax, eax
98 mov ax, ds
99 shl eax, 4
100 add eax, LABEL_DATA
101 mov word [LABEL_DESC_DATA + 2], ax
102 shr eax, 16
103 mov byte [LABEL_DESC_DATA + 4], al
104 mov byte [LABEL_DESC_DATA + 7], ah
105
106 ; 初始化堆栈段描述符
107 xor eax, eax
108 mov ax, ds
109 shl eax, 4
110 add eax, LABEL_STACK
111 mov word [LABEL_DESC_STACK + 2], ax
112 shr eax, 16
113 mov byte [LABEL_DESC_STACK + 4], al
114 mov byte [LABEL_DESC_STACK + 7], ah
115
116 ; 为加载 GDTR 作准备
117 xor eax, eax
118 mov ax, ds
119 shl eax, 4
120 add eax, LABEL_GDT ; eax <- gdt 基地址
121 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
122
123 ; 加载 GDTR
124 lgdt [GdtPtr]
125
126 ; 关中断
127 cli
128
129 ; 打开地址线A20
130 in al, 92h
131 or al, 00000010b
132 out 92h, al
133
134 ; 准备切换到保护模式
135 mov eax, cr0
136 or eax, 1
137 mov cr0, eax
138
139 ; 真正进入保护模式
140 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;在此由实模式切进保护模式;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
141 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
142
143;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
144
145LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
146 mov ax, cs
147 mov ds, ax
148 mov es, ax
149 mov ss, ax
150
151 mov sp, [SPValueInRealMode]
152
153 in al, 92h ; ┓
154 and al, 11111101b ; ┣ 关闭 A20 地址线
155 out 92h, al ; ┛
156
157 sti ; 开中断
158
159 mov ax, 4c00h ; ┓
160 int 21h ; ┛回到 DOS
161; END of [SECTION .s16]
162
163
164[SECTION .s32]; 32 位代码段. 由实模式跳入.
165[BITS 32]
166
167LABEL_SEG_CODE32:
168 mov ax, SelectorData
169 mov ds, ax ; 数据段选择子
170 mov ax, SelectorTest
171 mov es, ax ; 测试段选择子
172 mov ax, SelectorVideo
173 mov gs, ax ; 视频段选择子
174
175 mov ax, SelectorStack
176 mov ss, ax ; 堆栈段选择子
177
178 mov esp, TopOfStack
179
180
181 ; 下面显示一个字符串
182 mov ah, 0Ch ; 0000: 黑底 1100: 红字
183 xor esi, esi
184 xor edi, edi
185 mov esi, OffsetPMMessage ; 源数据偏移
186 mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
187 cld
188.1:
189 lodsb
190 test al, al
191 jz .2
192 mov [gs:edi], ax
193 add edi, 2
194 jmp .1
195.2: ; 显示完毕
196
197 call DispReturn
198
199 call TestRead
200 call TestWrite
201 call TestRead
202
203 ; 到此停止
204 ;**********注意在此由32位代码段跳至16位代码段**********************
205 jmp SelectorCode16:0
206
207; ------------------------------------------------------------------------
208TestRead:
209 xor esi, esi
210 mov ecx, 8
211.loop
212 mov al, [es:esi]
213 call DispAL
214 inc esi
215 loop .loop
216
217 call DispReturn
218
219 ret
220; TestRead 结束-----------------------------------------------------------
221
222
223; ------------------------------------------------------------------------
224TestWrite:
225 push esi
226 push edi
227 xor esi, esi
228 xor edi, edi
229 mov esi, OffsetStrTest ; 源数据偏移
230 cld
231.1:
232 lodsb
233 test al, al
234 jz .2
235 mov [es:edi], al
236 inc edi
237 jmp .1
238.2:
239
240 pop edi
241 pop esi
242
243 ret
244; TestWrite 结束----------------------------------------------------------
245
246
247; ------------------------------------------------------------------------
248; 显示 AL 中的数字
249; 默认地:
250; 数字已经存在 AL 中
251; edi 始终指向要显示的下一个字符的位置
252; 被改变的寄存器:
253; ax, edi
254; ------------------------------------------------------------------------
255DispAL:
256 push ecx
257 push edx
258
259 mov ah, 0Ch ; 0000: 黑底 1100: 红字
260 mov dl, al
261 shr al, 4
262 mov ecx, 2
263.begin:
264 and al, 01111b
265 cmp al, 9
266 ja .1
267 add al, '0'
268 jmp .2
269.1:
270 sub al, 0Ah
271 add al, 'A'
272.2:
273 mov [gs:edi], ax
274 add edi, 2
275
276 mov al, dl
277 loop .begin
278 add edi, 2
279
280 pop edx
281 pop ecx
282
283 ret
284; DispAL 结束-------------------------------------------------------------
285
286
287; ------------------------------------------------------------------------
288DispReturn:
289 push eax
290 push ebx
291 mov eax, edi
292 mov bl, 160
293 div bl
294 and eax, 0FFh
295 inc eax
296 mov bl, 160
297 mul bl
298 mov edi, eax
299 pop ebx
300 pop eax
301
302 ret
303; DispReturn 结束---------------------------------------------------------
304
305SegCode32Len equ $ - LABEL_SEG_CODE32
306; END of [SECTION .s32]
307
308
309; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
310[SECTION .s16code]
311ALIGN 32
312[BITS 16]
313LABEL_SEG_CODE16:
314 ; 跳回实模式:
315 ;****************注意在此用normal选择子对段寄存器进行填充******************************
316 mov ax, SelectorNormal
317 mov ds, ax
318 mov es, ax
319 mov fs, ax
320 mov gs, ax
321 mov ss, ax
322
323 mov eax, cr0
324 and al, 11111110b
325 mov cr0, eax
326
327;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;在此由保护模式切进实模式;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
328LABEL_GO_BACK_TO_REAL:
329 jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
330
331Code16Len equ $ - LABEL_SEG_CODE16
332
333; END of [SECTION .s16code]
334
注意一.在由保护模式切换到实模式之前,用normal选择子对段寄存器进行填充。
原因:
在切换到实模式之前,把一个指向似乎没有用的数据段的描述符Normal的选择子装载到DS和ES。这是为什么呢?
实模 式下 段描 述符 高速 缓冲 寄存 器的 内容
|
段寄存器
|
段基地址
|
段界限(固定)
|
段属性(固定)
|
存在性
|
特权级
|
已存取
|
粒度
|
扩展方向
|
可读性
|
可写性
|
可执行
|
堆栈大小
|
一致特权
|
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”表示以字方式操作堆栈。段基地址仍是 32位,其值是相应段寄存器值(段值)乘以16,在把段值装载到段寄存器时刷新。由于其值是16位段值乘上16,所以在实模式下基地址实际上有效位只有20位。每个段的32位段界限都固定为0FFFFH,段属性的许多位也是固定的。所谓固定是指在实方式下不可设置这些属性值,只能继续沿用保护方式下所设置的值。因此,在准备结束保护模式回到实模式之前,要通过加载一个合适的描述符选择子(如实例代码中的Normal选择子)到有关段寄存器,以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性。
也就是说,在实模式下装载段寄存器并不会影响段告诉缓冲寄存器的值,比如段界限(其实在实模式也没有必要改变,应为段界限一直都是0ffffh),这也就是为甚麽所有讲保护模式的树在讲到有保护模式切换到实模式时都要加载一个normal选择子的原因了。
应为必须在保护模式下设置好段高速缓冲寄存器的值,因为一旦到了实模式下就不能在改变了。
经我试验,对于normal的描述符,其最重要是段界限一定要设置为0ffffh,如果不是这样,那莫在由保护模式跳转到实模式后会发生错误(对于上述代码如果把normal描述符的段界限改为别的的话,在跳转后会产生死循环的现象,具体是什么原因现在还不明确,哪位高人知道一定要告诉我啊~~)。其次就是属性的设置一定要设置为可读可写的,否则也会发生错误.
注意二:不能从32位代码段返回实模式,而只能从16位代码段返回。
mov ax, SelectorNormal
317 mov ds, ax
318 mov es, ax
319 mov fs, ax
320 mov gs, ax
321 mov ss, ax
从上述代码可以看出:ds,es,fs,gs,ss这些段寄存器对应的高速缓冲寄存器中的内容可以通过加载normal选择子而得到更新,当向这
几个段寄存器中装入normal选择子时,会自动地将normal对应的描述符装载到描述符高速缓冲寄存器中,因normal选择子所对应的描述符的属性
符合实模式下的要求,即:段界限为ffffh,段属性也是固定的。
但是:CS寄存器是不可以通过装载normal来更新CS对应的高速缓冲寄存器的,为什么:你能写出这样的指令吗:MOV CS,SelectorNormal,汇编中没有这样的指令。
即然这样,那怎么可以让CS对应的高速缓冲寄存器中的内容符合实模式的要求呢?
方法是这样的:因为CS的值只能通过jmp,call这样的指令去改变,所以,定义一个16位的代码段,这个代码段的描述符定义为:段界限0ffffh,段属性:存在的只执行代码段
假设这个代码段的选择子为:select32,偏移地址为:offsetaddr
用一条转移指令向这段代码跳转:jmp select32:offsetaddr
因现在处于保护模式,所以jmp指令执行的结果是将select32对应的描述符装入cs对应的描述符高速缓冲寄存器中,这个描述符就是符合实模
式要求的。这样,CS段寄存器对应的高速缓冲寄存器中的内容终于达到实模式的要求了,所以现在可以进行从保护模式到实模式跳转了,再用一条jmp语句就可
以从保护模式跳转到实模式了。