您的位置:首页 > 编程语言

《Orange's 一个操作系统的实现》学习笔记--特权级代码段之间的转移(四)

2015-04-11 20:19 621 查看
一、演示任务内特权级变换的实例

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

我要说的:
如果从开始慢慢学到这里的话,看这个代码应该没什么大问题的,说一下调用栈里面的选择子显示CPL,TI值的代码

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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐