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

一、控制转移

控制转移基本上可分为两大类:同一任务内的控制转移和任务间的控制转移(任务切换)。同一任务内的控制转移又可分为:段内转移、特权级不变的段间转移和特权级变换的段间转移。段内转移与实模式下相似,不涉及特权级变换和任务切换。只有段间转移才涉及特权级变换和任务切换。本文介绍保护方式下的控制转移,重点是任务内的特权级变换和任务间的切换。 

<>任务内无特权级变换的转移

各种段内转移与实模式下相似,当然不涉及特权级变换和任务切换。只有各种形式的段间转移才涉及特权级变换和任务切换。

1.段间转移指令
与实模式下相同,指令 JMP、CALL 和 RET 都具有段间转移的功能,指令 INT 和 IRET 总是段间转移。此外,中断/异常也将引起

段间转移。有时把这些具有段间转移功能的指令统称为段间转移指令。在保护模式下,段间转移的目标位置由选择子和偏移构成的地址表示,常把它称为目标地址指针。在 32 位代码段中,上述指针内

的偏移使用 32 位表示,这样的指针也称为 48 位全指针。在实例二的 32 位代码段内就使用了 48 位全指针。在 16 位代码段中,上述指针内的偏移只使用 16 位表示。

与实模式下相似,段间转移指令 JMP 和段间调用指令 CALL 还可分为段间直接转移和段间间接转移两类。如果指令 JMP 和 CALL在指令中直接含有目标地址指针,那么就是段间直接转移;如果指令中含有指向包含目标地址指针的门描述符或 TSS 描述符的指针,那么就是段间间接转移,这种指针只有选择子部分有效,指示调用门、任务门或 TSS 描述符,而偏移部分不起作用。实际上,当段间转移指令 JMP 和段间调用指令 CALL 所含指针的选择子部分指示代码段描述符,那么就是段间直接转移,偏移部分表示目标代码段的入口点;当选择子部分指示门描述符或 TSS 描述符时,就是段间间接转移。

2.向目标代码段转移的步骤

  处理器在执行上述段间转移指令向目标代码段实施转移的过程中,一般至少要经过如下步骤:

(1)判断目标地址指针内的选择子指示的描述符是否为空描述符。空描述符是 GDT 中的第 0 个描述符,是一个特殊的描述符。目标代码段描述符不能为空描述符,也即选择子的高 14 位不能为 0。

(2)从全局或局部描述符表内读出目标代码段描述符。由选择子内的 TI 位,确定使用全局描述符表还是局部描述符表。(3)根据情况,检测描述符类型是否正确;调整 RPL。
(4)把目标代码段描述符内的有关内容装载到 CS 高速缓冲寄存器。(5)判断目标地址指针内的偏移是否越出代码段的界限。目标地址指针内的偏移必须不超过目标代码段界限。

(6)装载 CS 段寄存器和指令指针寄存器 EIP;CPL 存入 CS 内选择子的 RPL 字段。

上述步骤只是对转移过程的简单说明,实际的动作还要复杂。在把目标代码段描述符内的有关内容转载到 CS 高速缓冲寄存器时,还要进行如下保护检测,其中的 DPL 表示目标代码段描述符的特权级:

(1)对于非一致代码段,要求 CPL=DPL,RPL<=DPL;对于一致代码段,要求 CPL>=DPL。
(2)代码段必须存在,即描述符中的 P 位必须是 1。
通常描述符特权级 DPL 规定了对应段的特权级。如果描述符描述的是数据段,那么 DPL 就规定了访问该数据段的最外层特权级;

如果描述符描述的是代码段,那么 DPL 就规定了执行该代码段所需要的 CPL。但从上述装载 CS 高速缓冲寄存器时进行的保护检测可见,对于一致代码段,却要求 CPL>=DPL,也就是说,一致代码段描述符中的 DPL 规定了可以转移到一致代码段的最内层特权级。于是,3级的程序可以转移到任何一致的代码段,而 0 级的程序只允许转移到 DPL 等于 0 的一致代码段。一致代码段描述符内 DPL 的这种解释,正好与正常的 DPL 的解释相反。

  一致的可执行段是一种特别的段。这种存储段,为在多个特权级执行的程序,提供对子例程的共享支持,而不要求改变特权级。例
如,通过把数值库例程放在一致的代码段中,可以使不同级执行的程序共享数值库例程。这样,任何特权级的程序可以使用段间调用指
令,调用库中的例程,并在调用者所具有的特权级执行该例程。

3.任务内无特权级变换的转移

所谓任务内无特权级变换的转移指:在转移到新的代码段时,当前特权级 CPL 保持不变。利用段间转移指令 JMP、段间调用指令CALL 和段间返回指令 RET 可实现任务内无特权级变换的转移。利用 INT 指令和 IRET 指令也可实现任务内无特权级变换的转移。

(1)利用段间直接转移指令JMPCALL

在执行段间转移指令 JMP 时,如果指令内所含指针指示一个代码段,那么就直接开始上述向目标代码段转移的步骤;在执行段间调用指令 CALL 时,如果指令内所含指针指针指示一个代码段,那么就把返回地址指针压栈,然后就直接开始上述向目标代码段转移的步骤。顺利通过这几步(不调整 RPL)后,就完成了任务内无特权级变换的转移。

由此可见,利用段间直接转移指令 JMP 或调用指令 CALL 可方便地进行任务内无特权级变换的转移,但不能进行任务内特权级变换的转移。

(2)利用段间返回指令RET

在执行段间返回指令 RET 时,如果从堆栈中弹出的目标地址指针指示一个代码段,并且选择子符合 RPL=CPL 的条件,那么就开始上述向目标代码段的转移步骤。顺利通过这几步后,就完成了任务内无特权级变换的转移。

通常情况下,段间返回指令 RET 与段间调用指令 CALL 对应。在利用段间调用指令 CALL 以任务内无特权级变换的方式转移到某个子程序后,在子程序内利用段间返回指令 RET 以任务内无特权级变换的方式返回主程序。由于调用时无特权级变换,所以返回时也无特权级变换,如果真是如此,那么必须能够满足条件 RPL=CPL。

(3)利用调用门和其它途径

  如何利用调用门实行和其它方法实现任务内无特权级变换的转移将在后面的文章中介绍。

4.装载数据段和堆栈段寄存器时的特权检测

上面简单地说明了把选择子装入代码段寄存器 CS 时为实现保护而进行的检测,下面也简单地说明在把选择子装入数据段寄存器和堆栈段寄存器时要进行的检测。

在把选择子装入数据段寄存器 DS、ES、FS 或 GS 时,要进行如下检测:
(1)选择子不能为空;(2)选择子指定的描述符必须是数据段描述符、可读可执行的代码段或一致可读的可执行代码段的描述符;(3)对于数据段和可读可执行代码段,要求 CPL<=DPL,RPL<=DPL;

(4)对应的段必须存在。若装入的选择子不满足上述要求,则会产生异常。在把选择子装入堆栈段寄存器 SS 时要进行如下检测:(1)选择子不能为空;(2)选择子指定的描述符必须是可读写的数据段描述符;
(3)要求 CPL=DPL=RPL;
(4)对应段必须存在。若装入的选择子不满足上述条件,则在装入 SS 时就会引起异常。

<>演示任务内无特权级变换转移的实例(实例三)

在实例二中,32 位代码段到 16 位代码段的转移就是任务内无特权级转移的例子。

下面再给出一个用于演示任务内无特权级变换转移的实例。该实例使用了段间转移指令 JMP、段间调用指令 CALL 和段间返回指令RET 实现同一任务内相同特权级的转移。该实例还建立并使用了局部描述符表 LDT。

1.实现步骤和源程序

实现步骤如下:

(1)实模式下的初始化,包括对 GDT 和演示任务 LDT 的初始化,装载 GDTR;

(2)从实模式切换到保护模式,处于 0特权级;

(3)装载 LDTR,并设置堆栈;

(4)利用段间转移指令 JMP 实现从代码段 K 到同级代码段 L 的转移;

(5)利用段间调用指令 CALL调用同级代码段 C 中的子程序 D 显示字符串信息;

(6)利用段间调用指令 CALL 调用同级代码段 C 中的子程序 H 把十六进制数转换成对应的 ASCII 码;

(7)再利用段间调用指令 CALL 调用同级代码段 C 中的子程序 D 显示字符串信息;

(8)利用段间转移指令 JMP 实现从代码段 L 到代码段 K 的转移;

(9)从保护模式切换到实模式;(10)在实模式下结束程序。

该实例的逻辑功能是用十六进制数的形式显示代码段 L 的段界限的值。源程序如下: 


;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
;----------------------------------------------------------------------------
;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
;----------------------------------------------------------------------------
;存储段描述符类型值说明
;----------------------------------------------------------------------------
ATDR EQU 90h ;存在的只读数据段类型值
ATDW EQU 92h ;存在的可读写数据段属性值
ATDWA EQU 93h ;存在的已访问可读写数据段类型值
ATCE EQU 98h ;存在的只执行代码段属性值

;----------------------------------------------------------------------------
;系统段描述符类型值说明
;----------------------------------------------------------------------------
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(局部描述符表标志)

;----------------------------------------------------------------------------



.386p

;----------------------------------------------------------------------------
GDTSeg          SEGMENT PARA USE16 'GDT'          ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
GDT             LABEL   BYTE                      ;全局描述符表
DUMMY           Desc    <>                        ;空描述符
Normal          Desc    <0ffffh,,,ATDW,,>         ;规范段描述符
CodeK           Desc    <0ffffh,,,ATCE,,>         ;代码段K的描述符
LDTable         Desc    <LDTLen-1,,,ATLDT,,>      ;局部描述符表段的描述符
;----------------------------------------------------------------------------
GDTLen          =       $-GDT                     ;全局描述符表长度
;----------------------------------------------------------------------------
Normal_Sel      =       Normal-GDT                ;规范段描述符选择子
CodeK_Sel       =       CodeK-GDT                 ;代码段K的选择子
LDT_Sel         =       LDTable-GDT               ;局部描述符表段的选择子
;----------------------------------------------------------------------------
GDTSeg          ENDS                              ;全局描述符表段定义结束
;----------------------------------------------------------------------------
LDTSeg          SEGMENT PARA USE16 'LDT'          ;局部描述符表数据段(16位)
LDT             LABEL   BYTE                      ;局部描述符表
;代码段L的描述符
CodeL           Desc    <CodeLLen-1,CodeLSeg,,ATCE,,>  ;CodeLSeg:251c
;代码段C的描述符
CodeC           Desc    <CodeCLen-1,CodeCSeg,,ATCE,,>  ;CodeCSeg:251a
;显示缓冲区段描述符
VideoBuf        Desc    <0ffffh,0b800h,,ATDW,,>     ;b800
;LDT别名段描述符(DPL=3)
ToLDT           Desc    <LDTLen-1,LDTSEG,,ATDR+DPL3,,> ;LDTSEG=24d1
;显示信息缓冲区数据段描述符(DPL=3)
MData           Desc    <MDataLen-1,MDataSeg,,ATDW+DPL3,,> ;MDataSeg=24d4
;堆栈段描述符
StackS          Desc    <TopOfS-1,StackSeg,,ATDWA,,>      ;StackSeg=24d9
;----------------------------------------------------------------------------
LDTLen          =       $-LDT                     ;LDT所占字节数
LDNum           =       ($-LDT)/(SIZE Desc)       ;LDT含描述符项数
;----------------------------------------------------------------------------
CodeL_Sel       =       CodeL-LDT+TIL             ;代码段L的选择子
CodeC_Sel       =       CodeC-LDT+TIL             ;代码段C的选择子
Video_Sel       =       VideoBuf-LDT+TIL          ;显示缓冲区选择子
ToLDT_Sel       =       ToLDT-LDT+TIL             ;LDT别名段选择子
MData_Sel       =       MData-LDT+TIL+RPL3        ;显示信息数据段选择子
Stack_Sel       =       StackS-LDT+TIL            ;堆栈段选择子
;----------------------------------------------------------------------------
LDTSeg          ENDS                              ;局部描述符表段定义结束


;----------------------------------------------------------------------------
MDataSeg        SEGMENT PARA USE16 'MDATA'        ;显示信息缓冲区数据段
;----------------------------------------------------------------------------
Message         DB      'Value=',0
Buffer          DB      80 DUP(0)
MDataLen        =       $-Buffer
;----------------------------------------------------------------------------
MDataSeg        ENDS                              ;显示缓冲区数据段结束
;----------------------------------------------------------------------------
StackSeg        SEGMENT DWORD USE16 'STACK'       ;堆栈段
;----------------------------------------------------------------------------
                DW      512 DUP(?)
TopOfS          =       $-StackSeg
;----------------------------------------------------------------------------
StackSeg        ENDS                              ;堆栈段结束
;----------------------------------------------------------------------------
CodeCSeg        SEGMENT PARA USE16 'CODEC'        ;任务代码段C
                ASSUME  CS:CodeCSeg
;----------------------------------------------------------------------------
;显示信息子程序
;入口参数:fs:si指向要显示的以0结尾的字符串,es:di指向显示缓冲区
;----------------------------------------------------------------------------
DispMsg         PROC    FAR
                mov     ah,01001110b
Disp1:           mov     al,BYTE PTR fs:[si]
                inc     si
                or      al,al
                jz      Disp2
                mov     WORD PTR es:[di],ax
                inc     di
                inc     di
                jmp     Disp1
Disp2:           ret
DispMsg         ENDP
;----------------------------------------------------------------------------
;把AL寄存器低4位二进制数(一位16进制数)转换成ASCII码
;----------------------------------------------------------------------------
;DDA组合的BCD码的算术运算调整指令
;(1)如果AL中的低4位在A-F之间或者AF=1,则AL=AL+6,且AF位置1
;(2)如果AL中的高4位在A-F之间或者CF=1,则AL=AL+60h,且CF位置1
HToASCII        PROC    FAR
                and     al,0FH                  ;取低4位
                add     al,90h
                daa
                adc     al,40h			   ;带符号加法
                daa
                ret
HToASCII        ENDP
;----------------------------------------------------------------------------
CodeCLen        =       $-CodeCSeg
;----------------------------------------------------------------------------
CodeCSeg        ENDS                              ;代码段C定义结束
;----------------------------------------------------------------------------
CodeLSeg        SEGMENT PARA USE16 'CODEL'
                ASSUME  CS:CodeLSeg
;----------------------------------------------------------------------------
Virtual2        PROC    FAR
                mov     ax,Video_Sel              ;设置显示缓冲区指针
                mov     es,ax
                mov     di,1986
                mov     ax,MData_Sel              ;设置提示信息缓冲区指针
                mov     fs,ax
                mov     si,OFFSET Message
                CALL16  CodeC_Sel,DispMsg         ;显示提示信息
                mov     ax,ToLDT_Sel              ;把演示任务的LDT的别名
                mov     gs,ax                     ;段的描述符选择子装入GS
                mov     dx,WORD PTR gs:CodeL.LimitL
                mov     si,OFFSET Buffer          ;取代码段L的段界限值
                mov     cx,4                      ;并转成对应可显示字符串
Vir:            rol     dx,4    			          ;循环左移,分别取段界限低16位的高~低位(段界限在内存中存放的时候是低地址放内存低地址,高地址放内存高地址)
                mov     al,dl
                CALL16  CodeC_Sel,HToASCII
                mov     BYTE PTR fs:[si],al
                inc     si
                loop    Vir
                mov     WORD PTR fs:[si],'H'
                mov     si,OFFSET Buffer
                CALL16  CodeC_Sel,DispMsg
                JUMP16  CodeK_Sel,Virtual3
CodeLLen        =       $-CodeLSeg
Virtual2        ENDP
;----------------------------------------------------------------------------
CodeLSeg        ENDS
;----------------------------------------------------------------------------
CodeKSeg        SEGMENT PARA USE16 'CODEK'
                ASSUME  CS:CodeKSeg
;----------------------------------------------------------------------------
Virtual1        PROC    FAR
                mov     ax,LDT_Sel
                LLDT    ax                        ;加载局部描述符表寄存器LDTR
                mov     ax,Stack_Sel
                mov     ss,ax                     ;建立演示任务堆栈
                mov     sp,OFFSET TopOfS
                JUMP16  CodeL_Sel,Virtual2
Virtual3:         mov     ax,Normal_Sel
                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>
CodeKLen        =       $-CodeKSeg
Virtual1        ENDP
;----------------------------------------------------------------------------
CodeKSeg        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
;----------------------------------------------------------------------------
Start           PROC
                ASSUME  DS:GDTSeg   
                ;-----------------
                mov     ax,GDTSeg   ;GDTSeg=24cf
                mov     ds,ax
                ;初始化全局描述符表
                mov     bx,16		 
                mov     ax,CodeKSeg ;CodeKSeg=2521
                mul     bx   ;段值*16(偏移位0)
                mov     CodeK.BaseL,ax ;ds:0012
                mov     CodeK.BaseM,dl ;ds:0014
                mov     CodeK.BaseH,dh ;ds:0017
		
		  
                mov     ax,LDTSeg     ;LDTSeg=24d1
                mul     bx   ;段值*16  (偏移位0)
                mov     LDTable.BaseL,ax ;ds:001a
                mov     LDTable.BaseM,dl ;ds:001c
                mov     LDTable.BaseH,dh ;ds:001f
                ;设置GDT伪描述符
                ASSUME  DS:RDataSeg     
		  
                mov     ax,RDataSeg	;RDataSeg=2524
                mov     ds,ax 		
                mov     ax,GDTSeg	;GDTSeg=24cf
                mul     bx  ;段值*16  (偏移位0)
                mov     WORD PTR VGDTR.Base,ax  ;ds:0002
                mov     WORD PTR VGDTR.Base+2,dx ;ds:0004
                ;初始化演示任务LDT
                cld
                call    Init_MLDT
                ;保存实方式堆栈指针
                mov     SSVar,ss  ;ds:0008
                mov     SPVar,sp  ;ds:0006
                ;装载GDTR
                lgdt    FWORD PTR VGDTR
                cli
                ;切换到保护方式
                mov     eax,cr0
                or      al,1
                mov     cr0,eax
                JUMP16  <CodeK_Sel>,<OFFSET Virtual1>
Real:           ;又回到实方式
                mov     ax,RDataSeg
                mov     ds,ax
                lss     sp,DWORD PTR SPVar   ;恢复实模式下的ss,sp
                sti
                mov     ax,4c00h
                int     21h
Start           ENDP
;----------------------------------------------------------------------------
Init_MLDT       PROC
                push    ds
                mov     ax,LDTSeg
                mov     ds,ax
                mov     cx,LDNum
                mov     si,OFFSET LDT
InitL:           mov     ax,[si].BaseL
                movzx   eax,ax
                shl     eax,4    ;乘16
                shld    edx,eax,16   ;eax中高16位移到edx中
                mov     [si].BaseL,ax
                mov     [si].BaseM,dl
                mov     [si].BaseH,dh
                add     si,SIZE Desc
                loop    InitL
                pop     ds
                ret
Init_MLDT       ENDP
;----------------------------------------------------------------------------
RCodeSeg        ENDS
                END     Start  
这里给出的是Windows的代码,本来打算转换成linux上nasm格式,但是弄了很久程序也要死掉,测试的时候,为了简单注释了部分代码,可以成功运行一次(是一次,再次运行cpu就遇到非法指令),后来可以成功进入跳转进入保护模式,但是发现到call后又洗白了,后来又在linux上和Windows上反复测试,我发现:

nasm和masm在对标号解释的时候是不一样的。

masm:每个标号的地址是相对于当前段的偏移

nasm:则是该标号在整个程序中的偏移

所以对比会发现两者在初始化LDT和GDT的方式是不一样的。

masm:段基地址直接就是当前的段号(需要*16)

nasm:需要获取当前段(cs)地址+标号的偏移才构成基地址。

在masm中jmp和call可以直接 call(jmp) 段:标号,而nasm中则不可以,需要jmp(call) 段:相对当前段偏移(而不是全局偏移)

在代码中后面注释会有部分类似于ds:0012或者seg=24d1的信息,这个是我在测试代码过程中记录的此时操作对象为内存的地址或者段地址。

对了还有一点这段代码里面没有开关A20地址总线

程序运行结果(纯DOS下):





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值