一、演示任务内特权级变换的实例
下面给出一个演示任务内特权级变换的实例。该实例演示在任务内通过调用门从外层特权级变换到内层特权级;也演示通过段间返回指令从内层特权级变换到外层特权级;还演示通过调用门的无特权级变换的转移。实例使用了任务状态段 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.