一、演示任务内特权级变换的实例
下面给出一个演示任务内特权级变换的实例。该实例演示在任务内通过调用门从外层特权级变换到内层特权级;也演示通过段间返回指令从内层特权级变换到外层特权级;还演示通过调用门的无特权级变换的转移。实例使用了任务状态段 TSS,这是因为任务内特权级变换时要使用的内层堆栈指针存放在 TSS 中。
1.实现步骤
该实例的实现步骤为:
(1)实方式下初始化;
(2)切换到保护模式;
(3)设置 TR 和 LDTR。由于在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的 TSS 中,所以在进入保护
模式后设置任务状态段寄存器 TR。由于演示任务使用了局部描述符表,所以设置 LDTR;
(4)经调用门进入 32 位过渡代码段;
(5)建立返回 3 级演示代码段的环境;
(6)利用 RET 指令转移到 3 级的演示代码段。为了演示外层程序通过调用门调用内层程序,要使 CPL>0。实例先通过段间返回指令
RET 从特权级 0 变换到特权级 3 的演示代码段。在特权级 3 下,通过调用门调用 1 级的子程序。随着执行段间返回指令 RET,又回到 3级的演示代码段;
(7)在 3 级的演示代码段中,经调用门转移到 0 级的 32 位过渡代码段;(8)直接转 0 级的临时代码段;
(9)准备返回实模式;
(10)切换回实模式;
(11)实模式下的恢复工作。
2.源程序组织和清单
实例四由如下部分组成:
(1)全局描述符表 GDT。GDT 含有演示任务的 TSS 段描述符和 LDT 段描述符,此外还含有临时代码段的描述符、规范数据段描述符和视频缓冲区段描述符。
(2)演示任务的 LDT 段。它含有除临时代码段外的其它代码段的描述符和演示任务各级堆栈段描述符,还含有 3 个调用门。
(3)演示任务的 TSS 段。
(4)演示任务的 0 级、1 级和 3 级堆栈段。
(5)显示子程序段。32 位代码段,特权级 1。
(6)演示代码段。32 位代码段,特权级 3。
(7)过渡代码段。32 位段,特权级 0。
(8)临时代码段。16 位段,特权级 0。
(9)实模式下的数据和代码段。该实例的逻辑功能是显示演示代码段执行时的当前特权级 CPL和TI。源程序清单如下:
;windows
;16位偏移的段间直接转移指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
JUMP16 MACRO Selector,Offsetv
DB 0eah ;操作码
DW Offsetv ;16位偏移量
DW Selector ;段值或段选择子
ENDM
;----------------------------------------------------------------------------
;32位偏移的段间直接转移指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
COMMENT <JUMP32>
JUMP32 MACRO Selector,Offsetv
DB 0eah ;操作码
DD Offsetv
DW Selector ;段值或段选择子
ENDM
<JUMP32>
;-------------------------------------------------
JUMP32 MACRO Selector,Offsetv
DB 0eah ;操作码
DW Offsetv
DW 0
DW Selector ;段值或段选择子
ENDM
;----------------------------------------------------------------------------
;门描述符结构类型定义
;----------------------------------------------------------------------------
Gate STRUC
OffsetL DW 0 ;32位偏移的低16位
Selector DW 0 ;选择子
DCount DB 0 ;双字计数
GType DB 0 ;类型
OffsetH DW 0 ;32位偏移的高16位
Gate ENDS
;----------------------------------------------------------------------------
;16位偏移的段间调用指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
CALL16 MACRO Selector,Offsetv
DB 9ah ;操作码
DW Offsetv ;16位偏移量
DW Selector ;段值或段选择子
ENDM
;----------------------------------------------------------------------------
;32位偏移的段间调用指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
COMMENT <CALL32>
CALL32 MACRO Selector,Offsetv
DB 9ah ;操作码
DD Offsetv
DW Selector ;段值或段选择子
ENDM
<CALL32>
;-------------------------------------------------
CALL32 MACRO Selector,Offsetv
DB 9ah ;操作码
DW Offsetv
DW 0
DW Selector ;段值或段选择子
ENDM
;----------------------------------------------------------------------------
;存储段描述符结构类型定义
;----------------------------------------------------------------------------
Desc STRUC
LimitL DW 0 ;段界限(BIT0-15)
BaseL DW 0 ;段基地址(BIT0-15)
BaseM DB 0 ;段基地址(BIT16-23)
Attributes DB 0 ;段属性
LimitH DB 0 ;段界限(BIT16-19)(含段属性的高4位)
BaseH DB 0 ;段基地址(BIT24-31)
Desc ENDS
;----------------------------------------------------------------------------
;伪描述符结构类型定义(用于装入全局或中断描述符表寄存器)
;----------------------------------------------------------------------------
PDesc STRUC
Limit DW 0 ;16位界限
Base DD 0 ;32位基地址
PDesc ENDS
;----------------------------------------------------------------------------
;存储段描述符类型值说明
;----------------------------------------------------------------------------
D32 EQU 40h ;32位代码段标志
ATDR EQU 90h ;存在的只读数据段类型值
ATDW EQU 92h ;存在的可读写数据段属性值
ATDWA EQU 93h ;存在的已访问可读写数据段类型值
ATCE EQU 98h ;存在的只执行代码段属性值
ATCER EQU 9ah ;存在的可执行可读代码段属性值
;----------------------------------------------------------------------------
;系统段描述符类型值说明
;----------------------------------------------------------------------------
ATLDT EQU 82h ;局部描述符表段类型值
;----------------------------------------------------------------------------
;DPL值说明
;----------------------------------------------------------------------------
DPL0 EQU 00h ;DPL=0
DPL1 EQU 20h ;DPL=1
DPL2 EQU 40h ;DPL=2
DPL3 EQU 60h ;DPL=3
;----------------------------------------------------------------------------
;RPL值说明
;----------------------------------------------------------------------------
RPL0 EQU 00h ;RPL=0
RPL1 EQU 01h ;RPL=1
RPL2 EQU 02h ;RPL=2
RPL3 EQU 03h ;RPL=3
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;其它常量值说明
;----------------------------------------------------------------------------
TIL EQU 04h ;TI=1(局部描述符表标志)
AT386TSS EQU 89h ;可用386任务状态段类型值
AT386CGate EQU 8ch ;386调用门类型值
;----------------------------------------------------------------------------
.386p
GDTSeg SEGMENT PARA USE16 ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
;全局描述符表
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符
Normal Desc <0ffffh,,,ATDW,,>
;视频缓冲区段描述符(DPL=3)
VideoBuf Desc <07fffh,8000h,0bh,ATDW+DPL3,,>
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;任务状态段TSS描述符
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
;局部描述符表段的描述符
DemoLDTD Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
;临时代码段描述符
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,> ;注意它的存储段描述符
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表长度
GDNum = ($-EFFGDT)/(SIZE Desc) ;需特殊处理的描述符数
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;规范段描述符选择子
Video_Sel = VideoBuf-GDT ;视频缓冲区段描述符选择子
;----------------------------------------------------------------------------
DemoTSS_Sel = DemoTSS-GDT ;任务状态段描述符选择子
DemoLDT_Sel = DemoLDTD-GDT ;局部描述符表段的选择子
TempCode_Sel = TempCode-GDT ;临时代码段的选择子
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定义结束
;----------------------------------------------------------------------------
DemoLDTSeg SEGMENT PARA USE16 ;局部描述符表数据段(16位)
;----------------------------------------------------------------------------
DemoLDT LABEL BYTE ;局部描述符表
;0级堆栈段描述符(32位段)
DemoStack0 Desc <DemoStack0Len-1,DemoStack0Seg,,ATDW+DPL0,D32,>
;1级堆栈段描述符(32位段)
DemoStack1 Desc <DemoStack1Len-1,DemoStack1Seg,,ATDW+DPL1,D32,>
;3级堆栈段描述符(16位段)
DemoStack3 Desc <DemoStack3Len-1,DemoStack3Seg,,ATDW+DPL3,,>
;代码段描述符(32位段,DPL=3)
DemoCode Desc <DemoCodeLEN-1,DemoCodeSeg,,ATCE+DPL3,D32,>
;过渡代码段描述符(32位段)
T32Code Desc <T32CodeLen-1,T32CodeSeg,,ATCE,D32,>
;显示子程序代码段描述符(32位段,DPL=1)
EchoSubR Desc <EchoSubRLen-1,EchoSubRSeg,,ATCER+DPL1,D32,>
;----------------------------------------------------------------------------
DemoLDNum = ($-DemoLDT)/(SIZE Desc)
;----------------------------------------------------------------------------
;0级堆栈描述符选择子(RPL=0)
DemoStack0_Sel = DemoStack0-DemoLDT+TIL+RPL0
;1级堆栈描述符选择子(RPL=1)
DemoStack1_Sel = DemoStack1-DemoLDT+TIL+RPL1
;3级堆栈描述符选择子(RPL=3)
DemoStack3_Sel = DemoStack3-DemoLDT+TIL+RPL3
;代码段描述符选择子(RPL=3)
DemoCode_Sel = DemoCode-DemoLDT+TIL+RPL3
;过渡代码段描述符选择子
T32Code_Sel = T32Code-DemoLDT+TIL
;显示子程序代码段描述符选择子(RPL=1)
Echo_Sel1 = EchoSubR-DemoLDT+TIL+RPL1
;显示子程序代码段描述符选择子(RPL=3)
Echo_Sel3 = EchoSubR-DemoLDT+TIL+RPL3
;----------------------------------------------------------------------------
;指向过渡代码段内T32Begin点的调用门(DPL=0)
ToT32GateA Gate <T32Begin,T32Code_Sel,,AT386CGate,>
;指向过渡代码段内T32End点的调用门(DPL=3)
ToT32GateB Gate <T32End,T32Code_Sel,,AT386CGate+DPL3,>
;指向显示子程序代码段的调用门(DPL=3)
ToEchoGate Gate <EchoSUB,Echo_Sel3,,AT386CGate+DPL3,>
;----------------------------------------------------------------------------
DemoLDTLen = $-DemoLDT
;----------------------------------------------------------------------------
;指向过渡代码段内T32Begin点的调用门的选择子
ToT32A_Sel = ToT32GateA-DemoLDT+TIL
;指向过渡代码段内T32End点的调用门的选择子
ToT32B_Sel = ToT32GateB-DemoLDT+TIL
;显示子程序调用门的选择子
ToEcho_Sel = ToEchoGate-DemoLDT+TIL
;----------------------------------------------------------------------------
DemoLDTSeg ENDS ;局部描述符表段定义结束
;----------------------------------------------------------------------------
DemoTSSSeg SEGMENT PARA USE16 ;任务状态段TSS
;----------------------------------------------------------------------------
DD 0 ;Back
DW DemoStack0Len,0 ;0级堆栈指针
DW DemoStack0_Sel,0 ;初始化
DW DemoStack1Len,0 ;1级堆栈指针
DW DemoStack1_Sel,0 ;初始化
DD 0 ;2级堆栈指针
DD 0 ;未初始化
DD 0 ;CR3
DD 0 ;EIP
DD 0 ;EFLAGS
DD 0 ;EAX
DD 0 ;ECX
DD 0 ;EDX
DD 0 ;EBX
DD 0 ;ESP
DD 0 ;EBP
DD 0 ;ESI
DD 0 ;EDI
DW ?,0 ;ES
DW ?,0 ;CS
DW ?,0 ;SS
DW ?,0 ;DS
DW ?,0 ;FS
DW ?,0 ;GS
DW DemoLDT_Sel,0 ;LDT
DW 0 ;调试陷阱标志
DW $+2 ;指向I/O许可位图
DB 0ffh ;I/O许可位图结束标志
;----------------------------------------------------------------------------
DemoTSSLen = $-DemoTSSSeg
;----------------------------------------------------------------------------
DemoTSSSeg ENDS ;任务状态段TSS结束
;----------------------------------------------------------------------------
DemoStack0Seg SEGMENT DWORD STACK USE32 ;0级堆栈段(32位段)
DemoStack0Len = 512
DB DemoStack0Len DUP(0)
DemoStack0Seg ENDS ;0级堆栈段结束
;----------------------------------------------------------------------------
DemoStack1Seg SEGMENT DWORD STACK USE32 ;1级堆栈段(32位段)
DemoStack1Len = 512
DB DemoStack1Len DUP(0)
DemoStack1Seg ENDS ;1级堆栈段结束
;----------------------------------------------------------------------------
DemoStack3Seg SEGMENT DWORD STACK USE16 ;3级堆栈段(16位段)
DemoStack3Len = 512
DB DemoStack3Len DUP(?)
DemoStack3Seg ENDS ;3级堆栈段结束
;----------------------------------------------------------------------------
EchoSubRSeg SEGMENT PARA USE32 ;显示子程序代码段(32位,1级)
ASSUME CS:EchoSubRSeg
;----------------------------------------------------------------------------
Message DB 'CPL= TI=',0 ;显示信息(该代码段可读)
;----------------------------------------------------------------------------
EchoSub PROC FAR
cld
push ebp
mov ebp,esp
mov ax,Echo_Sel1 ;该代码段是可读段
mov ds,ax ;采用RPL=1的选择子
mov ax,Video_Sel
mov es,ax
mov edi,320 ;信息显示位置
mov esi,OFFSET Message
mov ah,4eh ;置显示属性(红底黄字)
EchoSub1: lodsb
or al,al
jz EchoSub2
stosw
jmp EchoSub1
EchoSub2: mov al,[ebp+8] ;从堆栈中取调用程序的选择子
and al,3 ;调用程序的CPL在CS的RPL字段
add al,'0'
mov ah,4eh ;置显示属性(红底黄字)
sub edi,10
stosw
add edi,8
mov al,[ebp+8] ;从堆栈中取调用程序的选择子
and al,1 ;调用程序TI字段
add al,'0'
mov ah,4eh
stosw
pop ebp
retf
EchoSub ENDP
;----------------------------------------------------------------------------
EchoSubRLen = $-EchoSubRSeg
;----------------------------------------------------------------------------
EchoSubRSeg ENDS ;显示子程序代码段结束
;----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE32 ;32位代码段(3级)
ASSUME CS:DemoCodeSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
CALL32 ToEcho_Sel,0 ;显示当前特权级(变换到1级)
CALL32 ToT32B_Sel,0 ;转到过渡代码段(变换到0级)
DemoBegin ENDP
DemoCodeLen = $-DemoCodeSeg
;----------------------------------------------------------------------------
DemoCodeSeg ENDS ;32位代码段结束
;----------------------------------------------------------------------------
T32CodeSeg SEGMENT PARA USE32 ;32位过渡代码段(0级)
ASSUME CS:T32CodeSeg
;----------------------------------------------------------------------------
T32Begin PROC FAR
mov ax,DemoStack0_Sel ;建立0级堆栈
mov ss,ax
mov esp,DemoStack0Len
push DWORD PTR DemoStack3_Sel ;压入3级堆栈指针
push DemoStack3Len
push DWORD PTR DemoCode_SEL ;压入入口点
push OFFSET DemoBegin
retf ;利用RET实现转3级的演示代码
T32Begin ENDP
;----------------------------------------------------------------------------
T32End PROC FAR
JUMP32 TempCode_Sel,<OFFSET ToReal>
T32End ENDP
T32CodeLen = $-T32CodeSeg
;----------------------------------------------------------------------------
T32CodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;16位临时代码段(0级)
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
mov ax,DemoTSS_Sel ;装载TR
ltr ax
mov ax,DemoLDT_Sel ;装载LDTR
lldt ax
JUMP16 ToT32A_Sel,0 ;通过调用门转过渡段
ToReal: mov ax,Normal_Sel ;准备切换回实模式
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
;----------------------------------------------------------------------------
TempCodeLen = $-TempCodeSeg
TempCodeSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;实方式数据段
VGDTR PDesc <GDTLen-1,> ;GDT伪描述符
SPVar DW ? ;用于保存实方式下的SP
SSVar DW ? ;用于保存实方式下的SS
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
CALL InitGDT ;初始化全局描述符表GDT
mov ax,DemoLDTSeg
mov fs,ax
mov si,OFFSET DemoLDT
mov cx,DemoLDNum
CALL InitLDT ;初始化局部描述符表LDT
mov SSVar,ss
mov SPVar,sp
lgdt FWORD PTR VGDTR ;装载GDTR并切换到保护方式
cli
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar ;又回到实方式
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
;初始化GDT表
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
;----------------------------------------------------------------------------
;初始化LDT表
;入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
;----------------------------------------------------------------------------
InitLDT PROC
ILDT: mov ax,WORD PTR FS:[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR fs:[si].BaseL,ax
mov BYTE PTR fs:[si].BaseM,dl
mov BYTE PTR fs:[si].BaseH,dh
add si,SIZE Desc
loop ILDT
ret
InitLDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start
程序中部分片段的背景和实现方法在前面的实例中做过介绍,下面主要就如何实现任务内特权级变换做些说明:
(1)通过段间返回指令实现特权级变换
实例在两处使用段间返回指令实现任务内的特权级变换。一处是在 0 级的过渡代码段中用段间 RET 指令从特权级 0 变换到特权级 3的演示代码段。该处 RET 指令并不对应 CALL 指令。实例从实模式切换到保护模式后 CPL=0。为了演示如何通过调用门调用内层程序,要设法使 CPL>0。为此,实例先建立一个已发生的从外层到内层变换的环境,即按上图所示在当前堆栈(0 级堆栈)中放入外层堆栈的指
针和外层演示程序的入口指针,形成一个如下图所示的 0 级堆栈,无需传递参数。然后,执行段间返回指令 RET,从堆栈中弹出 3 级演示代码段的选择子,RPL=3,而当时 CPL=0,所以导致向外层变换特权级,从 0 级的过渡代码段变换到 3 级的演示代码段,同时切换到3 级堆栈。
另一处是从 1 级的显示子程序 EchoSub 返回到 3 级的演示程序段。该处的 RET 指令与演示程序中使用的通过调用门的段间调用指令 CALL 相对应,执行段间返回指令 RET 时的 1 级堆栈也如上图所示,其中的返回地址和外层栈指针由 CALL 指令压入。
(2)通过调用门实现特权级变换
实例在两处使用了段间调用指令,通过调用门实现特权级的变换。一处是 3 级演示代码通过调用门 ToEchoGate 调用 1 级的显示子程序。调用门 ToEchoGate 自身的 DPL=3,只有这样,3 级的演示代码才能够使用该调用门。由于调用门内的选择子 Echo_Sel3 所指示的显示子程序代码段描述符 DPL=1,而当时 CPL=3,所以引起从外层特权级向内层特权级的变换,使 CPL=1。同时形成如上图所示的 1级堆栈。虽然调用门内的选择子 Echo_Sel3 的 RPL=3,大于目标代码段的 DPL,但没有关系,因为在通过调用门转移时,门内指示目标代码段的选择子 RPL 总被作 0 对待。
另一处是 3 级演示代码还通过调用门 ToT32GateB 调用了 0 级的过渡代码。该处使用的调用门描述符 DPL 也等于 3。由于调用门内的选择子 T32Code_Sel 所指示的过渡代码段描述符的 DPL=0,而当时 CPL=3,所以引起从 3 特权级向 0 特权级的变换,使 CPL=0。同时形成如上图所示的 0 级堆栈。但该处的调用实际上是 “有去无回”的,调用的目的是转移到 0 级的过渡代码,准备返回实模式。由于从 3级的演示代码到 0 级的过渡代码要发生特权级变换,所以不能使用转移指令 JMP,必须使用调用指令 CALL。
(3)通过调用门实现无特权级变换
在临时代码段中,使用调用门 ToT32GateA 转移到过渡代码段。尽管调用门内的选择子 T32Code_Sel 所指示的过渡代码段描述符的
DPL=0,但当时 CPL=0,所以不发生特权级变换。正是这个原因,才可以使用段间转移指令 JMP。
(4)子程序EchoSub的实现
子程序 EchoSub 的功能是显示调用程序执行时的特权级。调用程序的执行特权级在代码段寄存器 CS 内选择子的 RPL 字段,在调用EchoSub 时,CS 寄存器的内容被压入堆栈。子程序从堆栈取得调用程序的代码段选择子,再从中分离出 RPL 就可得调用程序的执行特权级。
(5)装载任务状态段寄存器TR
在任务内发生特权级变换时堆栈也随着自动切换,外层堆栈指针保存在内层堆栈中,而内层堆栈指针存放在当前任务的 TSS 中。所以,在从外层向内层变换时,要访问 TSS(从内层向外层转移时不需要访问 TSS,而只需内层栈中保存的栈指针)。实例在进入保护模式下的临时代码段后,通过如下两条指令装载任务状态段寄存器 TR,使其指向已预置好的任务的 TSS:
mov ax,DemoTSS_Sel
ltr ax
LTR 指令是专门用于装载任务状态段寄存器 TR 的指令。该指令的操作数是对应 TSS 段描述符的选择子。LTR 指令从 GDT 中取出
相应的 TSS 段描述符,把 TSS 段描述符的基地址和界限等信息装入 TR 的高速缓冲寄存器中。
EchoSub2: mov al,[ebp+8] ;从堆栈中取调用程序的选择子
and al,3 ;调用程序的CPL在CS的RPL字段
add al,'0'
mov ah,4eh ;置显示属性(红底黄字)
sub edi,10
stosw
add edi,8
mov al,[ebp+8] ;从堆栈中取调用程序的选择子
and al,1 ;调用程序TI字段
add al,'0'
mov ah,4eh
stosw
pop ebp
retf
为什么是ebp+8?调用这段代码的是
CALL32 ToEcho_Sel,0 ;显示当前特权级(变换到1级)
该代码所在段是3级,转到1级代码段,发生特权级变换,会切换栈,将当前的段选择子(被扩展为32位)和偏移(32位)压入3级栈,进入到1级代码段,由外层栈切换到内层栈,然后进行参数复制,复制开始压入的段选择子和偏移,然后push ebp,1级栈里面存的就是这些了示意图如下:
目前还有一个问题未弄明白:
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,> ;注意它的存储段描述符
我在测试的时候发现它的选择子的段界限不能是TempCodeLen-1(或TempCodeLen),前者直接让DOS崩溃了,TempCodeLen+4到是可以正常输出,但是就是没有办法正常返回,段界限为0ffffh就可以正常执行并返回,难道是存在访问越界,被处理器终止或者访问出错吗,这点现在我还没有很好的想明白。
运行结果:
下面是书上的代码(Linux平台)在特权级切换的同时输出字符(这里我做了一点小改动)
这里的value是特权级为3的选择子的输出,本来打算直接在ring3代码段中输出,后来发现DOS会崩溃,突然才发现数据段CPL=0,而ring3的CPL=3,不能访问数据段,所以就干脆放到了CPL=0的代码段(LABEL_SEG_CODE32)中输出了。
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限 1 (2 字节)
dw %1 & 0FFFFh ; 段基址 1 (2 字节)
db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节)
db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节)
%endmacro ; 共 8 字节
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
DA_32 EQU 4000h ; 32 位段
DA_DPL3 EQU 60h ; DPL = 3
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR EQU 90h ; 存在的只读数据段类型值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
DA_C EQU 98h ; 存在的只执行代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT EQU 82h ; 局部描述符表段类型值
DA_TaskGate EQU 85h ; 任务门类型值
DA_386TSS EQU 89h ; 可用 386 任务状态段类型值
DA_386CGate EQU 8Ch ; 386 调用门类型值
; 选择子类型值说明
; 其中:
; SA_ : Selector Attribute
SA_RPL0 EQU 0 ; ┓
SA_RPL1 EQU 1 ; ┣ RPL
SA_RPL2 EQU 2 ; ┃
SA_RPL3 EQU 3 ; ┛
SA_TIG EQU 0 ; ┓TI
SA_TIL EQU 4 ; ┛
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_CODE_DEST: Descriptor 0, SegCodeDestLen-1, DA_C+DA_32 ;非一致,32
LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ;Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 ;Stack,32
LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3
LABEL_DESC_LDT: Descriptor 0, LDTLen-1, DA_LDT ;LDT
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ;TSS
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3
; 门 目标选择子, 偏移, DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3
; 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
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT + SA_RPL3
; 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 "value=", 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]
; 堆栈段ring3
[SECTION .s3]
ALIGN 32
[BITS 32]
LABEL_STACK3:
times 512 db 0
TopOfStack3 equ $ - LABEL_STACK3 - 1
; END of [SECTION .s3]
; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN 32
[BITS 32]
LABEL_TSS:
DD 0 ; Back
DD TopOfStack ; 0 级堆栈
DD SelectorStack ;
DD 0 ; 1 级堆栈
DD 0 ;
DD 0 ; 2 级堆栈
DD 0 ;
DD 0 ; CR3
DD 0 ; EIP
DD 0 ; EFLAGS
DD 0 ; EAX
DD 0 ; ECX
DD 0 ; EDX
DD 0 ; EBX
DD 0 ; ESP
DD 0 ; EBP
DD 0 ; ESI
DD 0 ; EDI
DD 0 ; ES
DD 0 ; CS
DD 0 ; SS
DD 0 ; DS
DD 0 ; FS
DD 0 ; GS
DD 0 ; LDT
DW 0 ; 调试陷阱标志
DW $ - LABEL_TSS + 2 ; I/O位图基址
DB 0ffh ; I/O位图结束标志
TSSLen equ $ - LABEL_TSS
; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[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, cs
shl eax, 4
add eax, LABEL_SEG_CODE_DEST
mov word [LABEL_DESC_CODE_DEST + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_DEST + 4], al
mov byte [LABEL_DESC_CODE_DEST + 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
; 初始化堆栈段描述符(ring3)
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK3
mov word [LABEL_DESC_STACK3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK3 + 4], al
mov byte [LABEL_DESC_STACK3 + 7], ah
; 初始化 LDT 在 GDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_LDT
mov word [LABEL_DESC_LDT + 2], ax
shr eax, 16
mov byte [LABEL_DESC_LDT + 4], al
mov byte [LABEL_DESC_LDT + 7], ah
; 初始化 LDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_A
mov word [LABEL_LDT_DESC_CODEA + 2], ax
shr eax, 16
mov byte [LABEL_LDT_DESC_CODEA + 4], al
mov byte [LABEL_LDT_DESC_CODEA + 7], ah
; 初始化Ring3描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_RING3
mov word [LABEL_DESC_CODE_RING3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_RING3 + 4], al
mov byte [LABEL_DESC_CODE_RING3 + 7], ah
; 初始化 TSS 描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_TSS
mov word [LABEL_DESC_TSS + 2], ax
shr eax, 16
mov byte [LABEL_DESC_TSS + 4], al
mov byte [LABEL_DESC_TSS + 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 eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, 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
add edi, 2
jmp .1
.2: ; 显示完毕
call DispReturn
;显示LABEL_CODE_RING3的CPL
mov ax,SelectorData
mov ds,ax
mov esi,OffsetStrTest
mov ax,SelectorVideo
mov es,ax
mov ah,4eh ;置显示属性(红底黄字)
EchoSub1:
lodsb
or al,al
jz EchoSub2
stosw
jmp EchoSub1
EchoSub2:
mov ax,SelectorCodeRing3
and al,3 ;调用程序的CPL在CS的RPL字段
add al,'0'
mov ah,4eh ;置显示属性(红底黄字)
mov [gs:edi], ax
; Load TSS
mov ax, SelectorTSS
ltr ax ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
retf ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。
; ------------------------------------------------------------------------
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]
[SECTION .sdest]; 调用门目标段
[BITS 32]
LABEL_SEG_CODE_DEST:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'C'
mov [gs:edi], ax
; Load LDT
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]
; 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]
; LDT
[SECTION .ldt]
ALIGN 32
LABEL_LDT:
; 段基址 段界限 , 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
LDTLen equ $ - LABEL_LDT
; LDT 选择子
SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
; END of [SECTION .ldt]
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
LABEL_CODE_A:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 13 + 0) * 2 ; 屏幕第 13 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'L'
mov [gs:edi], ax
; 准备经由16位代码段跳回实模式
jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
; END of [SECTION .la]
; CodeRing3
[SECTION .ring3]
ALIGN 32
[BITS 32]
LABEL_CODE_RING3:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 14 + 0) * 2 ; 屏幕第 14 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, '3'
mov [gs:edi], ax
call SelectorCallGateTest:0 ; 测试调用门(有特权级变换),将打印字母 'C'。
SegCodeRing3Len equ $ - LABEL_CODE_RING3
; END of [SECTION .ring3]
在代码段LABEL_DESC_CODE32中有这么一段代码。
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
retf ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。
后面三句语句都理解吧,就是把选择子,偏移压栈,利用retf指令返回,但是为什么要前两个语句了,这里需要回顾一下:
《Orange's 一个操作系统的实现》学习笔记--特权级代码段之间的转移(二)里面的内容,看下面图示:
有ring0到ring3转移过程中,会把调用者的ss,esp恢复,并且esp调整,废除开始前入栈的参数,但是我们这里没有从ring3进入ring0,返回从ring0直接进入ring3,所以这里我们需要自己手动的把目的栈的选择子和偏移压入栈,这样才能顺利执行,这里没有函数参数,所以返回后不用调整esp.