下面以pmtest2.asm为例,来讲述“实模式--保护模式--实模式”的转换过程。
1、“实模式--保护模式--实模式”的转换过程。
2、介绍段描述符属性
3、pm.inc 中的宏定义
4、pmtest2.asm源代码
一、“实模式--保护模式--实模式”的转换过程
1、“实模式--保护模式”的跳转
(1)关中断
(2)打开地址线A20
(3)置cr0寄存器的末位为1
(4)实现跳转,进入到保护模式
2、“保护模式--实模式”的跳转
(1)从保护模式下的32位代码段跳转到16位代码段
(2)在16位代码段下初始化所有段寄存器
(3)置cr0的末位为0
(4)实现跳转,返回到实模式
3、pmtest2.asm中“实模式--保护模式--实模式”的跳转过程
(1)下面是“实模式--保护模式--实模式”的跳转过程图
其中 LABEL_BEGIN、LABEL_REAL_ENTRY运行于实模式下;LEBEL_SEG_CODE32、LABEL_SEG_CODE16运行于保护模式下。所以该程序实现了“实模式--保护模式--实模式”的跳转。
(2)“实模式--保护模式”的跳转
这个跳转过程主要存在于LABEL_BEGIN代码段中:
70- 71行:保存实模式下的SP内的值
73-111行:初始化段描述符
113-121行:加载GDTR
123-124行:关中断
126-129行:打开地址线A20
141-144行:准备切换到保护模式,置cr0的末位为1
146-147行:跳转到保护模式
(3)“保护模式--实模式”的跳转
208-209行:从保护模式下的32位代码段跳转到16位代码段
316-322行:为跳回实模式做准备,将段寄存器初始化为符合实模式下的代码段规范。即用SelectorNormal来初始化段寄存器。
324-326行:置cr0的末位为0
328-329行:实现跳转,进入到实模式下的 LABEL_REAL_ENTRY段
158-158行:恢复实模式下的SP
160-162行:关闭A20地址线
164-164行:开中断
166-168行:返回DOS
二、段描述符的属性
段描述符属性占5、6字节,其具体特性如下:
在这里主要介绍第5字节的内容。
1、 P:存在(Present)位。
1 表示段在内存中存在
0 表示段在内存中不存在
2、 DPL:表示描述符特权级(Descriptor Privilege level),共2位。
它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
3、 S:说明描述符的类型。
1 数据段和代码段描述符
0系统段描述符和门描述符
4、 TYPE:说明存储段描述符所描述的存储段的具体属性
数据段类型:
类型值 说明
---------------------------------
0 只读
1 只读、已访问
2 读/写
3 读/写、已访问
4 只读、向下扩展
5 只读、向下扩展、已访问
6 读/写、向下扩展
7 读/写、向下扩展、已访问
代码段类型:
类型值 说明
---------------------------------
8 只执行
9 只执行、已访问
A 执行/读
B 执行/读、已访问
C 只执行、一致码段
D 只执行、一致码段、已访问
E 执行/读、一致码段
F 执行/读、一致码段、已访问
系统段类型:
类型编码 说明
----------------------------------
0 <未定义>
1 可用286TSS
2 LDT
3 忙的286TSS
4 286调用门
5 任务门
6 286中断门
7 286陷阱门
8 未定义
9 可用386TSS
A <未定义>
B 忙的386TSS
C 386调用门
D <未定义>
E 386中断门
F 386陷阱门
三、pm.inc中的宏定义
在程序中,我们定义属性时,使用了pm.inc中的宏定义DA_DRW、DA_C、DA_32、DA_DRW、DA_DRWA。下面让我来向大家解释这些宏定义。
DA : Descriptor Attribute
D : 数据段
C : 代码段
S : 系统段
R : 只读
RW : 读写
A : 已访问
-----------------------------------------------------------
DA_32 EQU 4000h ; 32 位段
DA_DPL0 EQU 00h ; DPL = 0
DA_DPL1 EQU 20h ; DPL = 1
DA_DPL2 EQU 40h ; DPL = 2
DA_DPL3 EQU 60h ; DPL = 3
-----------------------------------------------------------
存储段描述符类型值说明
-----------------------------------------------------------
DA_DR EQU 90h ; 存在的只读数据段类型值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值
DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值
DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
-----------------------------------------------------------
系统段描述符类型值说明
-----------------------------------------------------------
DA_LDT EQU 82h ; 局部描述符表段类型值
DA_TaskGate EQU 85h ; 任务门类型值
DA_386TSS EQU 89h ; 可用 386 任务状态段类型值
DA_386CGate EQU 8Ch ; 386 调用门类型值
DA_386IGate EQU 8Eh ; 386 中断门类型值
DA_386TGate EQU 8Fh ; 386 陷阱门类型值
四、pmtest2.asm源代码:
;=====================================================
;pmtest2.asm
;编译方法:nasm pmtest2.asm -o pmtest2.com
%include "pm.inc" ;常量,宏,以及一些说明
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
;GDT
; 段基址 段界限 段属性
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ;Normal描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C + DA_32 ;非一致代码段,32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ;非一致代码段,16
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW + DA_DPL1 ;Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ;Stack,32位
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW ;测试代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ;显存首地址
;GDT 结束
GdtLen equ $ - LABEL_GDT ;GDT长度
GdtPtr dw GdtLen - 1 ;GDT界限
dd 0 ;GDT基地址
;GDT选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT + SA_RPL3
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
;END of [SECTION .gdt]
[SECTION .data1] ;数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
;字符串
PMMessage: db "In Protect Mode now.", 0 ;进入保护模式后显示该字符串
OffsetPMMessage equ PMMessage - $
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $
DataLen equ $ - LABEL_DATA
;END of [SECTION .data1]
;全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
;END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL + 3], ax
mov [SPValueInRealMode], sp
;初始化16位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word[LABEL_DESC_CODE16+2], ax
shr eax, 16
mov byte[LABEL_DESC_CODE16+4], al
mov byte[LABEL_DESC_CODE16+7], ah
;初始化32位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word[LABEL_DESC_CODE32+2], ax
shr eax, 16
mov byte[LABEL_DESC_CODE32+4], al
mov byte[LABEL_DESC_CODE32+7], ah
;初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word[LABEL_DESC_DATA+2], ax
shr eax, 16
mov byte[LABEL_DESC_DATA+4], al
mov byte[LABEL_DESC_DATA+7], ah
;初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word[LABEL_DESC_STACK+2], ax
shr eax, 16
mov byte[LABEL_DESC_STACK+4], al
mov byte[LABEL_DESC_STACK+7], ah
;为加载GDTR作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt
mov dword[GdtPtr+2], eax ; [GdtPtr+2] <- gdt 基地址
;加载GDTR
lgdt [GdtPtr]
;关中断
cli
;打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
;清屏操作(用以指定色彩)
mov ah, 06H ;功能06H和07H
mov ch, 00 ;功能描述:初始化屏幕或滚屏
mov cl, 00 ;入口参数:AH=06H——向上滚屏,07H——向下滚屏
mov dh, 24 ;AL=滚动行数(0——清窗口)
mov dl, 79 ;BH=空白区域的缺省属性
mov bh, 7 ;(CH、CL)=窗口的左上角位置(Y坐标,X坐标)
mov al, 00 ;(DH、DL)=窗口的右下角位置(Y坐标,X坐标)
int 10H ;出口参数:无
;准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
;真正进入保护模式
jmp dword SelectorCode32:0 ;执行这一句会把SelectorCode32装入cs,并跳转到SelectorCode32:0处
;......................................................................
;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
LABEL_REAL_ENTRY: ;从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ;关闭A20地址线
and al, 11111101b
out 92h, al
sti ;开中断
;回到DOS
mov ax, 4C00h
int 21h;
;END of [SECTION .s16]
[SECTION .s32] ;32位代码段,由实模式跳入
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ;数据段选择子
mov ax, SelectorTest
mov es, ax ;测试段选择子
mov ax, SelectorVideo
mov gs, ax ;视频段选择子
mov ax, SelectorStack
mov ss, ax ;堆栈段选择子
mov esp, TopOfStack
;下面显示一个字符串
mov ah, 0Ch ;0000:黑底 1100:红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ;源数据偏移
mov edi, (80*10+0)*2 ;目的数据偏移。屏幕第10行,第0列
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax ;将ax中的数据送入到显存
add edi, 2
jmp .1
.2: ;显示完毕
call DispReturn
call TestRead
call TestWrite
Call TestRead
;到此停止
jmp SelectorCode16:0
;---------------------------------------------------------------------------
TestRead:
xor esi, esi
mov ecx, 8
.loop:
mov al, [es:esi]
call DispAL
inc esi
loop .loop
call DispReturn
ret
;TestRead结束--------------------------------------------------------------
;--------------------------------------------------------------------------
TestWrite:
push esi
push edi
xor esi, esi
xor edi, edi
mov esi, OffsetStrTest ;源数据偏移
.1:
lodsb
test al, al
jz .2
mov [es:edi], al
inc edi
jmp .1
.2:
pop edi
pop esi
ret
;TestWrite结束--------------------------------------------------------------
;---------------------------------------------------------------------------
;显示AL中的数字
;默认地:
; 数字已经存在AL中
; edi始终指向要显示的下一个字符的位置
;被改变的寄存器
; ax, edi
;---------------------------------------------------------------------------
DispAL:
push ecx
push edx
mov ah, 0Ch
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .1
add al, '0'
jmp .2
.1:
sub al, 0Ah
add al, 'A'
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
add edi, 2
pop edx
pop ecx
ret
;DispAL结束-----------------------------------------------------------------
;---------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop ebx
pop eax
ret
;DispReturn结束--------------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
;16位代码段,由32位代码段跳入,跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
;跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ;段地址会在程序开始处被设置成正确的值
Code16Len equ $-LABEL_SEG_CODE16
;END of [SECTION .s16code]