• x86保护模式-六 控制转移


    控制转移可以分为两大类  :同一任务内的控制转移    和   任务间的控制转移(任务切换)

    同一个任务内的控制转移可以分为段内转移 、特权级不变的段间转移特权级改变的段间转移
    段内转移与实模式相同 不涉及特权级变换和任务切换 
    只有段间转移才涉及特权级变换和任务切换任务  重点为任务内的特权级变换和任务间的切换
     
    一 任务内无特权级变换的转移
    1.段间转移指令
    jmp  call    ret    int    iret   都具有段间转移功能   另外中断和异常也将引起段间转移   其中int和iret(中断和中断返回)总是段间转移。
    段间转移的目标位置由选择子和偏移构成的地址标识   常常成为目标地址指针
    在32位代码段中,偏移使用32位表示  成为48位全指针   16位代码段中 ,使用偏移为16位
    段间转移又可以分为段间直接转移和段间间接转移两大类
    jmp和call在指令中直接含有目标地址指针,那么就是段间直接转移
    指令中含有指向目标地址指针的门描述符或tss描述符的指针   就是段间间接转移,此时只有选择子部分有效指示调用门,任务门或tss描述符,而偏移部分不起作用。
    当jmp  call  含有的选择子部分 指示代码段描述符,那么就是段间直接转移   偏移部分表示目标代码段的入口点。
    选择子部分指示门描述符或tss描述符时,就是段间间接转移。
    2.向目标代码段转移的步骤
    a 判断目标地址指针内的选择子指示的描述符是否为空描述符 gdt中的第0个描述符 是一个特殊的描述符 不能为空
    描述符选择子的高14位不能为0
    b 从gdt表和ldt表中读出目标代码段描述符 由 选择子内的ti位决定使用gdt还是ldt
    c 检测描述符类型是否正确 再调整rpl
    d 把目标代码段描述符内的有关内容装载到cs高速缓冲寄存器
    e 判断目标地址指针内的偏移是否超越代码段的界限 目标地址指针内的偏移必须不超过目标代码段界限
    f 转载cs段寄存器和指令指针寄存器eip cpl存入cs内的选择子的rpl字段
    装载cs的高速缓冲寄存器时 还要进行保护监测 其中dpl表示目标代码段描述符的特权级
    1.对于非一致代码段 要求cpl=dpl rpl<=dpl 对于一致代码段 要求cpl>=dpl
    2.代码段必须存在 即描述符中的p位为1
    dpl规定了对应段的特权级 如果是数据段 dpl就规定访问该数据段的最外层特权级
    如果是代码段 dpl就规定了执行该代码段所需要的cpl
    对于一致代码段 要求cpl>=dpl 即一致代码段描述符中的dpl规定了可以转移到一致代码段的最内层特权级 于是 3级的程序可以转移到任何
    一致的代码段 而0级的程序只允许转移到dpl等于0的一致代码段 一致代码段描述符中dpl与正常的dpl的解释相反
    一致代码段是特殊的段    为多个特权级执行的程序  提供子程序的共享支持   而不要求改变特权级    
    例如   通过把数值库例程放在一致的代码段中   可以不同级执行的程序共享数值库例程。
    3.任务内无特权级变换的转移
    在转移到新的代码段时,当前特权级cpl保持不变   jmp   call   ret   int   iret   都可以实现任务内无特权级变换的转移
    a 利用jmp和call
    jmp 指令内指示一个代码段   就直接向目标代码转移
    call  指示代码段   就把返回地址指针压栈   然后再想代码段转移   不调整rpl   完成任务内无特权级变换的转移
    b 利用ret段间返回
    从堆栈中弹出的目标地址指针指示一个代码段    并且rpl=cpl    就向目标代码段转移
    与call对应    由于call调用时无特权级的变换   所以返回时也无特权级变换    所以必须能够满足rpl=cpl
    c 利用调用门和其他途径
    4.装载数据段和堆栈段寄存器时的特权检测
    上面说明把选择子装入cs的保护检测
    下面说明把选择子装入数据段寄存器和堆栈段寄存器的保护检测
    a 选择子不能为空
    b 描述符必须为数据段   可读可执行的代码段或一致可读的可执行代码段的描述符
    c 对于数据段和可读可执行代码段  要求cpl<=dpl   rpl<=dpl
    d 对应的段必须存在
    堆栈段ss
    a 同上
    b 描述符必须为可读写的数据段描述符
    c cpl=dpl=rpl
    d 对应段必须存在
     
    下面的程序演示任务内无特权级变换转移的实例
    步骤1.实模式的初始化 包括对gdt和ldt的初始化 即定义数据结构 装载gdtr
    2.从实模式切换到保护模式 处于0特权级
    3.转载ldtr 设置堆栈
    4.利用jmp 从codek到codel段的转移 权级不变
    5.利用call 调用同级codec中的子程序d 显示字符串信息
    6.利用call调用同级codec中的子程序h 把16禁止数转换成对应的ascii码
    7.利用call调用codec断种的子程序d显示字符串信息
    8.利用jmp 从codel段到codek段的转移
    9.从保护模式切换到实模式
    10.在实模式下结束程序
    ;名称:ASM3.ASM
    ;功能:演示任务内无特权级变换的转移
    ;编译:TASM ASM3.ASM
    ;连接:TLINK ASM3.OBJ
    ;----------------------------------------------------------------------------
    INCLUDE         386SCD.INC						//包含的预定义的文件
    ;----------------------------------------------------------------------------
    GDTSeg          SEGMENT PARA USE16 'GDT'          ;全局描述符表数据段(16位)    //gdt初始化
    ;----------------------------------------------------------------------------
    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                ;规范段描述符选择子   相对于gdt表首部的偏移位置即选择子的值
    CodeK_Sel       =       CodeK-GDT                 ;代码段K的选择子
    LDT_Sel         =       LDTable-GDT               ;局部描述符表段的选择子
    ;----------------------------------------------------------------------------
    GDTSeg          ENDS                              ;全局描述符表段定义结束
    
    
    
    
    ;----------------------------------------------------------------------------
    LDTSeg          SEGMENT PARA USE16 'LDT'          ;局部描述符表数据段(16位)   ldt初始化
    LDT             LABEL   BYTE                      ;局部描述符表
    ;代码段L的描述符
    CodeL           Desc    <CodeLLen-1,CodeLSeg,,ATCE,,>
    ;代码段C的描述符
    CodeC           Desc    <CodeCLen-1,CodeCSeg,,ATCE,,>
    ;显示缓冲区段描述符
    VideoBuf        Desc    <0ffffh,0b800h,,ATDW,,>
    ;LDT别名段描述符(DPL=3)
    ToLDT           Desc    <LDTLen-1,LDTSEG,,ATDR+DPL3,,>		//注意别名的使用  对ldt段行驶不同的访问方式
    ;显示信息缓冲区数据段描述符(DPL=3)
    MData           Desc    <MDataLen-1,MDataSeg,,ATDW+DPL3,,>      //指向mdata段   
    ;堆栈段描述符
    StackS          Desc    <TopOfS-1,StackSeg,,ATDWA,,>		//指向堆栈段
    ;----------------------------------------------------------------------------
    LDTLen          =       $-LDT                     ;LDT所占字节数		
    LDNum           =       ($-LDT)/(SIZE Desc)       ;LDT含描述符项数   	
    ;----------------------------------------------------------------------------
    CodeL_Sel       =       CodeL-LDT+TIL             ;代码段L的选择子    til在386SCD.INC中定义  04h   即ti=1
    CodeC_Sel       =       CodeC-LDT+TIL             ;代码段C的选择子
    Video_Sel       =       VideoBuf-LDT+TIL          ;显示缓冲区选择子
    ToLDT_Sel       =       ToLDT-LDT+TIL             ;LDT别名段选择子
    MData_Sel       =       MData-LDT+TIL+RPL3        ;显示信息数据段选择子   +rpl3的原因是什么?指示访问级别
    Stack_Sel       =       StackS-LDT+TIL            ;堆栈段选择子
    ;----------------------------------------------------------------------------
    LDTSeg          ENDS                              ;局部描述符表段定义结束
    
    
    ;----------------------------------------------------------------------------
    MDataSeg        SEGMENT PARA USE16 'MDATA'        ;显示信息缓冲区数据段    初始化信息数据段
    ;----------------------------------------------------------------------------
    Message         DB      'Value=',0			//?
    Buffer          DB      80 DUP(0)    			//占80个字节长度
    MDataLen        =       $				//?
    ;----------------------------------------------------------------------------
    MDataSeg        ENDS                              ;显示缓冲区数据段结束
    ;----------------------------------------------------------------------------
    
    
    StackSeg        SEGMENT DWORD USE16 'STACK'       ;堆栈段   初始化堆栈段
    ;----------------------------------------------------------------------------
                    DW      512 DUP(?)         		//占用1024个字节
    TopOfS          =       $				//栈顶
    ;----------------------------------------------------------------------------
    StackSeg        ENDS                              ;堆栈段结束
    
    
    ;----------------------------------------------------------------------------
    CodeCSeg        SEGMENT PARA USE16 'CODEC'        ;任务代码段C     //codec代码段
                    ASSUME  CS:CodeCSeg
    ;----------------------------------------------------------------------------
    ;显示信息子程序
    ;入口参数:fs:si指向要显示的以0结尾的字符串,es:di指向显示缓冲区
    ;----------------------------------------------------------------------------
    DispMsg         PROC    FAR			//子程序1
                    mov     ah,01001110b		//ah=0x4eh
    Disp1:          mov     al,BYTE PTR fs:[si]	//源数据字节
                    inc     si
                    or      al,al			//al=0结束
                    jz      Disp2			//返回  终结条件
                    mov     WORD PTR es:[di],ax	//字传送到es指向的显示段
                    inc     di			//di指针自加2
                    inc     di
                    jmp     Disp1			//循环显示
    Disp2:          ret
    DispMsg         ENDP
    ;----------------------------------------------------------------------------
    ;把AL寄存器低4位二进制数(一位16进制数)转换成ASCII码
    ;----------------------------------------------------------------------------
    HToASCII        PROC    FAR			//转换成ascii码   子程序2
                    and     al,00001111b		//al 高4位为0   低位不变
                    add     al,90h			//al=高位变9
                    daa				//十进制数加法调整  用于调整al的值 
                    adc     al,40h			//
                    daa				// 调整   bcd码
                    ret				//返回
    HToASCII        ENDP
    ;----------------------------------------------------------------------------
    CodeCLen        =       $
    ;----------------------------------------------------------------------------
    CodeCSeg        ENDS                              ;代码段C定义结束    代码段c仅仅定义了两个被调用的子程序
    ;----------------------------------------------------------------------------
    
    
    CodeLSeg        SEGMENT PARA USE16 'CODEL'      ;  codeL段的定义   被跳转
                    ASSUME  CS:CodeLSeg
    ;----------------------------------------------------------------------------
    Virtual2        PROC    FAR
                    mov     ax,Video_Sel              ;设置显示缓冲区指针  es指向
                    mov     es,ax		//将选择子赋值给段寄存器  硬件自动计算指向段的段基地址
                    mov     di,1986				//1986从何而来	es:di
                    mov     ax,MData_Sel              ;设置提示信息缓冲区指针
                    mov     fs,ax
                    mov     si,OFFSET Message		fs:si
                    CALL16  CodeC_Sel,DispMsg         ;显示提示信息  value=0 此字符串
                    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			  //循环左移  移除的位进入cf   还要填补空出的位 4位移转换 从高开始
                    mov     al,dl			//赋值给al
                    CALL16  CodeC_Sel,HToASCII	//调用子程序 十六进制转换到ascii码利用al的低四位进行运算 并返回
                    mov     BYTE PTR fs:[si],al	//将rol循环出的高4位 变为对应字符后填写到buufer区域
                    inc     si			//buffer 指针自加	
                    loop    Vir			//循环到vir  重新开始运算下个ascii码
                    mov     WORD PTR fs:[si],'H'	//在循环结束后  ascii码串的末尾加上h字符  进行十六进制字符标识
                    mov     si,OFFSET Buffer	//si复位  指向buffer的基地址
                    CALL16  CodeC_Sel,DispMsg	//调用显示提示信息子程序  重新显示十进制对应的字符串
                    JUMP16  CodeK_Sel,Virtual3	//调用子程序回到实模式前的准备工作  利用normal_sel重置寄存器
    CodeLLen        =       $
    Virtual2        ENDP
    ;----------------------------------------------------------------------------
    CodeLSeg        ENDS
    ;----------------------------------------------------------------------------
    
    
    
    
    CodeKSeg        SEGMENT PARA USE16 'CODEK'  		;codeK 代码段的定义
                    ASSUME  CS:CodeKSeg
    ;----------------------------------------------------------------------------
    Virtual1        PROC    FAR				//被调用
                    mov     ax,LDT_Sel			//下面为将ldt的位于gdt中的选择子送给ldtr寄存器
                    LLDT    ax                        ;加载局部描述符表寄存器LDTR   选择子进入ldtr寄存器
                    mov     ax,Stack_Sel  			//将堆栈的选择子送给ss寄存器
                    mov     ss,ax                     ;建立演示任务堆栈
                    mov     sp,OFFSET TopOfS
                    JUMP16  CodeL_Sel,Virtual2	//跳转到16位代码段  选择子为codeL_SEL 指向的段
    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        =       $
    Virtual1        ENDP
    ;----------------------------------------------------------------------------
    CodeKSeg        ENDS 						;codeK段定义结束
    
    
    
    
    ;============================================================================	
    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    			 //ds定位到GDT表的首部
                    ;-----------------			
                    mov     ax,GDTSeg			
                    mov     ds,ax				
                   ;初始化全局描述符表		需要实模式下进行
                    mov     bx,16				
                    mov     ax,CodeKSeg			//codek段段基地址
                    mul     bx				//ax左移四位;高位在dx,低16位在ax
                    mov     CodeK.BaseL,ax			
                    mov     CodeK.BaseM,dl
                    mov     CodeK.BaseH,dh			//32位段基地址分别赋值
                    mov     ax,LDTSeg			//LDTSeg    表格所在的数据段的基地址给ax
                    mul     bx				
                    mov     LDTable.BaseL,ax			//32位段基地址赋值
                    mov     LDTable.BaseM,dl
                    mov     LDTable.BaseH,dh
                    ;设置GDT伪描述符		
                    ASSUME  DS:RDataSeg			//rdataseg   为实模式数据段   存放gdt的伪描述符
                    mov     ax,RDataSeg			//
                    mov     ds,ax				//ds寄存器存放此段基地址
                    mov     ax,GDTSeg			
                    mul     bx				
                    mov     WORD PTR VGDTR.Base,ax		//段基地址赋值   将GDT表的基地址赋值为伪描述符
                    mov     WORD PTR VGDTR.Base+2,dx		//相应字段   以至加载GDTR
                    ;初始化演示任务LDT			//初始化
                    cld					//清除si  di的方向
                    call    Init_MLDT			//调用   ldt表中各项初始化后返回
                    ;保存实方式堆栈指针
                    mov     SSVar,ss
                    mov     SPVar,sp
                    ;装载GDTR
                    lgdt    QWORD PTR VGDTR
                    cli    //关闭中断
                    ;切换到保护方式
                    mov     eax,cr0
                    or      al,1
                    mov     cr0,eax
                    JUMP16  <CodeK_Sel>,<OFFSET Virtual1>   //跳转到codek代码段  16位  偏移为virtual1
    Real:           ;又回到实方式
                    mov     ax,RDataSeg
                    mov     ds,ax
                    lss     sp,DWORD PTR SPVar
                    sti
                    mov     ax,4c00h
                    int     21h
    Start           ENDP
    ;----------------------------------------------------------------------------
    
    
    
    
    Init_MLDT       PROC				//初始化MLDT    仅初始化各个ldt表中各个描述符的基地址
                    push    ds
                    mov     ax,LDTSeg
                    mov     ds,ax
                    mov     cx,LDNum
                    mov     si,OFFSET LDT
    InitL:          mov     ax,[si].BaseL		//初始化L  注意此方式为实模式下进行初始化 16位*4 变为20位
                    movzx   eax,ax
                    shl     eax,4
                    shld    edx,eax,16		//双精度移位指令   三个操作数   edx左移16位  空出的位有eax.
                    mov     [si].BaseL,ax		//的高16位进行填充   eax值不变
                    mov     [si].BaseM,dl		//32位基地址	 
                    mov     [si].BaseH,dh
                    add     si,SIZE Desc		//  指针加上描述符的长度 8个字节
                    loop    InitL			//当cx为0时终止
                    pop     ds
                    ret
    Init_MLDT       ENDP
    ;----------------------------------------------------------------------------
    
    
    RCodeSeg        ENDS
                    END     Start
    
    
    
    上面程序的说明
    任务   实模式下初始化LDT
    上面的任务使用了LDT   本实例中该LDT在实模式下初始化  (也可以在保护模式下初始化),在定义ldt表时已经初始化了LDT表中的描述符的界限和属性   ,之后再利用一个子程序设置各个段的基地址。
    为方便  在定义时把各个段的值安排在相应的描述符的段基地址低16位字段中。由于实例中各段在实模式下定位,所以段值乘以16就是对应的段基地址
    2.装载LDTR寄存器
    在使用ldt表之前  需要先装载局部描述符到寄存器LDTR   
    mov ax,LDT_SEL
    lldt  ax      //用来装载gdt中的ldt选择子到ldtr寄存器
    将指向的ldt基地址和界限等信息   装入ldtr的高速缓冲寄存器
    由于要引用gdt   所以此时的模式应该是在保护模式下执行  而不能在实模式下执行
    3.利用段间转移指令jmp实现任务内无特权级变化的转移
    当进入到保护模式后  特权级为0   
    jump16 codel_sel,virtual2   指定跳转到的选择子和偏移
    codel_sel  为L代码段的描述符的选择子
    virtual2为段中的偏移地址
    选择子中的描述符指示位 TI位为1   特权级为0  表示对应的代码段的特权级是0   请求特权级也是0  目标代码段不是一直代码段   所以cpl=dpl   rpl<=dpl  的情况下 能顺利进行相同特权级的转移
    目标代码段的选择子codel_sel 被装入cs   对应的描述符中的信息被装入到高速缓冲寄存器  偏移量virtual2被装入指针寄存器 ip   
    由于是16位代码段  所以偏移用16位表示
    4.利用段间调用指令call实现任务内无权变的转移
    call16 codec_sel,dispmsg
    codec在ldt中 dpl为0 所以codec_sel的rpl也是0 目标c不是一直代码段 所以cpl=dpl rpl<=dpl
    codec_sel被装入cs   偏移dispmsg被装入ip
    5.别名技术
    使用了两个描述符来描述演示任务的LDT段   段描述符LDTable被安排在GDT中   它是系统段的描述符  
    把段LDTSeg描述成演示任务的局部描述符表LDT   描述符ToLDT被安排在LDT中,是数据段描述符,把段LDTSeg描述成一个普通数据段。描述符LDTable被装载到LDTR,描述符ToLDT被装载到某个数据段寄存器
    当需要访问任务的局部描述符LDT段  以取得代码段L的段界限值  需要通过某个段寄存器进行  但是不能把系统段的描述符的选择子装载到段寄存器   所以需要用两个描述符来描述ldtseg段
    例如:用两个具有不同类型值的描述符来描述同一个段    或者用两个具有不同dpl的描述符来描述同一个段
     
     
    二 任务内不同特权级的的变换
    在一个任务内存在四种特权级    
    例如外层的应用程序调用内层操作系统的子程序   以获得必要的例如存储器分配等系统服务   内层操作系统的程序完成后  返回到外层应用程序
    同一个任务内   实现特权级从外层到内层变换的途径是使用段间调用call指令   通过调用门进行转移  ,然后从内层到外层变换的普通途径是使用段间返回指令ret   注意:不能用jmp指令实现任务内不同特权级的变换
    1.通过调用门进行转移
    当jmp和call所含指针的选择子指示调用门描述符时   就可以实现通过调用门的转移,但是只有call指令能变换到内层的特权级  jmp指令只能转移到同级别的代码
    调用门描述符转移的入口点包含目标地址的段级偏移量的48位全指针   在执行通过任务门的段间转移指令jmp或call时,指令所含指针内的选择子用来确定调用门,偏移被丢弃   把调用门内的48位全指针作为目标地址指针进行转移
    同样需要特权级的检查    只有在相同级或者更内层特权级的程序才可访问调用门  即cpl<=调用门的dpl     而且指示门的选择子的rpl必须满足rpl<=门的dpl
    满足上面的条件  才进行转移   注意调用门的描述符中的dpl与调用门描述符指向的目标代码段的dpl不为同一个值
    调用门内的选择子指示的描述符必须是代码段描述符     装载代码段描述符高速缓冲寄存器之前调整代码段选择子的rpl=0.   也即调用门中代码段选择子的rpl被忽略
    在装载cs高速缓冲寄存器时,要对目标代码段描述符进行保护检测   dpl已经不是调用门dpl   而是调用门内选择子所指示的目标代码段描述符的dpl      
     
    段间调用指令call和段间转移指令jmp所做的检测不一样
    调用门使用jmp    设置rpl=0    所以rpl<=dpl的条件总能满足  所以对于普通的非一致代码段   当cpl=dpl时   发生无特权级别的转移;对于一致代码段  在满足cpl>=dpl时也发生无权变转移   其他情况为异常
    调用门使用call    rpl=0    rpl<=dpl   对于一致段   cpl>=dpl发生无权变转移    对于非一致段  当cpl=dpl时仍然发生无权变转移   当cpl>dpl时   就发生向内层权变的转移    此时将调用门中的选择子和偏移装入cs和指令指针eip中  并且使cpl保持等于dpl  同时切换到内层堆栈。
    结论:call   可以实现外层到内岑过得切换
    jmp只能实现无权变的切换
    调用门也可以实现无权变的转移
    call和jmp都不能实现向外层级别的转移    会引起异常
    call指令将目标的cs和eip加载前   需要把原cs和eip 保存到堆栈  没如果是无权级变换  堆栈不变   ;如果权变,需要保存到内层级别的堆栈中
     
    2.堆栈的切换
    call指令通过调用门向内层转移   权级变化  转换到一个新的代码段  而且也切换到内层的堆栈段  
    在特权级发生内层变换时   根据变换到的级别使用的tss中相应的堆栈指针对ss及esp寄存器进行初始化  建立一个空栈
    a 建立起内层堆栈后   cpu要先把外层堆栈的指针ss及esp寄存器的值压入到内层栈   以使得相应的内层返回可恢复原来的外层堆栈。
    b 再从外层栈复制以双字为单位的调用参数到内层栈中,调用门中dcount字段值决定了复制参数的数量。这些被复制的参数是主程序通过堆栈传递为子程序的实参,在调用之前被压入外层栈。通过复制过来的参数  可以让内层的子程序不需要考虑堆栈的切换,而容易地访问主程序传递过来的实参。
    c 最后调用的返回值被压入堆栈   在调用结束时返回使用
    图中的选择子被扩展成为32位    高16位为0    对于16位的使用调用门的段如此
     
    注意:无论是否使用调用门   只要不发生权变   就不会切换堆栈
     
    3.向外层返回
    与使用call指令 通过调用门向内层变换相反,使用ret指令实现向外层返回。
    a 从堆栈中弹出返回地址  并且可以采用调整esp的方法  跳过相应的在调用之前压入堆栈的参数  
    b 返回地址的选择子指示要返回的代码段的描述符   从而确定代码段
    c 选择子的rpl确定返回后的特权级   而不是对应描述符的dpl   
    原因为:ret可能使控制返回到一致代码段    而一致代码段可以在dpl规定的特权级外的级别上执行   
    ret指令所用的返回地址   只能是使用代码段描述符,而不能使用任何系统段描述符或门描述符   当然更不能使用数据段描述符   否则引起异常   与call指令向对应   ret指令不能向内层返回
    步骤:
    1 ret指令先从堆栈中弹出返回地址   如果弹出的选择子的rpl规定相对于cpl更外层的特权级  那么就引起向外层返回
    2 为向外层返回,跳过内层堆栈中的参数  再从内层栈中弹出指向外层堆栈的指针  并装入ss及esp   以恢复外层堆栈
    3. 调整esp   跳过调用之前压入外层堆栈的参数   即返回指令不但弹出内层栈的参数   而且也弹出外层栈的参数
    4. 检查数据段寄存器ds es  fs   及gs 以保证寻址的段在外层是可以访问的     如果段寄存器寻址的段在外层是不可访问的  那么 装入一个空选择子   以避免在返回时发生保护空洞
    5 返回外层继续执行
    上述五步  是对带立即数的段间返回指令而言的  立即数规定了 堆栈中要跳过的参数的字节数。
    对于无立即数的段间返回指令缺少第二步和第三步。如果ret指令不需要向外层返回  那么就只有1和5步   对于有通过堆栈传递参数的子程序   必须使用带立即数的返回指令返回,否则返回时会装载错误的外层指针。
    如果不使用带立即数的返回指令 可以在返回前把外层栈的栈指针存入内层栈中的用于保存返回地址上方的两个双字区域中
    ,由外层返回的过程可知,这可以正确恢复外层栈的指针 ,但是在外层程序中,必须人为调整外层栈指针,以废除在外层
    栈中压入的参数。在使用c调用约定的程序中可以使用此方法。但是会增加代码的长度和处理时间,代码执行的效率降低。
    任务内权级变换的实例 四
    任务内通过调用门从外层变换到内层;
    通过段间返回指令从内层变换到外层;
    通过调用门的无权变的转移。
    实例使用了任务状态段TSS,内层堆栈指针需要存放在TSS中
    1.实现步骤
    a实模式下初始化   gdt   
    b切换到保护模式   cr0
    c设置tr和ldtr  由于在任务内发生权级变换要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以进入保护模式后设置任务状态段寄存器tr。由于任务使用了局部描述符表,所以需要设置ldtr
    d经调用门进入32位过度代码段
    e建立返回3级代码段的环境
    f利用ret 转移到3级代码段,为了演示外层程序通过调用门调用内层程序,要使cpl>0   ,实例先通过段间返回指令ret从特权级0变换到特权级3的代码段。在级别3下,通过调用门调用1级的子程序,执行ret  又回到3级的代码段
    g在3级代码段中,经调用门转移到0级的32位过渡代码段
    h直接转0级的临时代码段
    i准备返回到实模式
    j切换回事模式
    k实模式下的恢复工作
    2.组织清单
    gdt    包含tss   ldt   tempcode    normal   videobuffer
    ldt stack    gate
    tss段
    0 1 3级stack段
    显示subprog段    32位代码段   权级为1
    演示code段 32位 权级3
    过渡code段   32位 权级0
    tempcode 16位 权级0
    实模式下的data 和code段
     
    实例的逻辑功能显示演示代码段执行时的当前特权级cpl
    ;----------------------------------------------------------------------------
    INCLUDE         386SCD.INC    							//包含文件
    ;----------------------------------------------------------------------------
    GDTSeg          SEGMENTPARAUSE16;全局描述符表数据段(16位)	//gdt数据段开始;----------------------------------------------------------------------------;全局描述符表
    GDT             LABELBYTE;空描述符
    DUMMY           Desc    <>			//空描述符;规范段描述符
    Normal          Desc    <0ffffh,,,ATDW,,>	//描述符1   规范描述符;视频缓冲区段描述符(DPL=3)
    VideoBuf        Desc    <07fffh,8000h,0bh,ATDW+DPL3,,>//描述符2  显示缓冲区;----------------------------------------------------------------------------
    EFFGDT          LABELBYTE;任务状态段TSS描述符
    DemoTSS         Desc    <DemoTssLen-1,DemoTSSSeg,,AT386TSS,,>	//演示任务描述符;局部描述符表段的描述符
    DemoLDTD        Desc    <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>	//演示局部描绘符表的描述符;临时代码段描述符
    TempCode        Desc    <0ffffh,TempCodeSeg,,ATCE,,>		//演示代码描述符;----------------------------------------------------------------------------
    GDTLen          =$-GDT                     ;全局描述符表长度	//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
                    DD      DemoStack0Len             ;0级堆栈指针
                    DD      DemoStack0_Sel            ;初始化
                    DD      DemoStack1Len             ;1级堆栈指针
                    DD      DemoStack1_Sel            ;初始化
                    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      DemoLDT_Sel               ;LDT
                    DW      0                         ;调试陷阱标志
                    DW      $+2                       ;指向I/O许可位图
                    DW      0ffffh                    ;I/O许可位图结束标志
    ;----------------------------------------------------------------------------
    DemoTSSLen      =       $
    ;----------------------------------------------------------------------------
    DemoTSSSeg      ENDS                              ;任务状态段TSS结束
    
    
    ;----------------------------------------------------------------------------
    DemoStack0Seg   SEGMENT DWORD STACK USE32         ;0级堆栈段(32位段)
    DemoStack0Len   =       512
                    DB      DemoStack0Len DUP(?)
    DemoStack0Seg   ENDS                              ;0级堆栈段结束
    ;----------------------------------------------------------------------------
    DemoStack1Seg   SEGMENT DWORD STACK USE32         ;1级堆栈段(32位段)
    DemoStack1Len   =       512
                    DB      DemoStack1Len DUP(?)
    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=',0                  ;显示信息(该代码段可读)  当前运行的级别   不是dpl
    ;----------------------------------------------------------------------------
    EchoSub         PROC    FAR
                    cld					//清除方向   正向操作
                    push    ebp				//esp一直指向栈顶   ebp指向存储堆栈中值的位置
                    mov     ebp,esp				//1级堆栈
                    mov     ax,Echo_Sel1              ;该代码段是可读段
                    mov     ds,ax                     ;采用RPL=1的选择子  ds:esi源指针  显示子程序代码段的message位置
                    mov     ax,Video_Sel
                    mov     es,ax
                    mov     edi,1996			//es:edi   目的指针  指向显示缓冲区段
                    mov     esi,OFFSET Message
                    mov     ah,4eh                    ;置显示属性(红底黄字)   中断功能号码
    EchoSub1:       lodsb				//加载ds:si字节到al
                    or      al,al
                    jz      EchoSub2
                    stosw				//写ax字到es:di
                    jmp     EchoSub1
    EchoSub2:       mov     eax,[ebp+8]               ;从堆栈中取调用程序的选择子  加8的原因是什么?
                    and     al,3                      ;调用程序的CPL在CS的RPL字段
                    add     al,'0'
                    mov     ah,4eh                    ;置显示属性(红底黄字)
                    stosw
                    pop     ebp
                    retf
    EchoSub         ENDP
    ;----------------------------------------------------------------------------
    EchoSubRLen     =       $
    ;----------------------------------------------------------------------------
    EchoSubRSeg     ENDS                              ;显示子程序代码段结束
    
    
    ;----------------------------------------------------------------------------
    DemoCodeSeg     SEGMENTPARAUSE32;32位代码段(3级)    关键位置ASSUMECS:DemoCodeSeg
    ;----------------------------------------------------------------------------
    DemoBegin       PROCFARCALL32  ToEcho_Sel,0;显示当前特权级(变换到1级)CALL32  ToT32B_Sel,0;转到过渡代码段(变换到0级)
    DemoBegin       ENDP
    DemoCodeLen     =$;----------------------------------------------------------------------------
    DemoCodeSeg     ENDS;32位代码段结束
    
    
    ;----------------------------------------------------------------------------
    T32CodeSeg      SEGMENTPARAUSE32;32位过渡代码段(0级)ASSUMECS:T32CodeSeg
    ;----------------------------------------------------------------------------
    T32Begin        PROCFAR                     	//创建一个  返回的环境    返回到3级权限的代码段movax,DemoStack0_Sel         ;建立0级堆栈movss,axmovesp,DemoStack0Len
                    pushDWORDPTR DemoStack3_Sel  ;压入3级堆栈指针push    DemoStack3Len
                    pushDWORDPTR DemoCode_SEL    ;压入入口点pushOFFSET DemoBegin
                    retf;利用RET实现转3级的演示代码
    T32Begin        ENDP;----------------------------------------------------------------------------
    T32End          PROCFARJUMP32  TempCode_Sel,<OFFSET ToReal>  //0级
    T32End          ENDP
    T32CodeLen      =$;----------------------------------------------------------------------------
    T32CodeSeg      ENDS
    
    
    ;----------------------------------------------------------------------------
    TempCodeSeg     SEGMENTPARAUSE16;16位临时代码段(0级)ASSUMECS:TempCodeSeg
    ;----------------------------------------------------------------------------
    Virtual         PROCFARmovax,DemoTSS_Sel            ;装载TRltrax				//选择子装载到tr寄存器movax,DemoLDT_Sel            ;装载LDTRlldtax				//选择子进ldt寄存器JUMP16  ToT32A_Sel,0 ;通过调用门转过渡段   偏移无用ToReal:movax,Normal_Sel             ;准备切换回实模式  准备工作movds,axmoves,axmovfs,axmovgs,axmovss,axmoveax,cr0andal,11111110bmovcr0,eaxJUMP16<SEG Real>,<OFFSET Real>
    Virtual         ENDP
    TempCodeLen     =$;----------------------------------------------------------------------------
    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    QWORD 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
    ;----------------------------------------------------------------------------
    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
    ;----------------------------------------------------------------------------
    ;入口参数: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.通过段间返回指令实现特权级的变换
    T32Begin处建立一个从3级转到0级的堆栈的情况,此时cpl=0之后ret返回到3级程序中,并未对应真实的call调用指令   
    返回地址是democode_sel选择子指向的代码段  (级别为3级)
     
    另一处是从1级的显示子程序Echosub返回到3级的演示程序段。该处的RET指令与演示程序中使用的通过调用门的段间调用指令CALL相对应,执行段间返回的ret时的堆栈也如上图一样
     
    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的实现
    功能是显示调用程序执行时的特权级的值。调用程序的执行级别在寄存器cs内选择子的rpl字段,在调用此程序时,cs的内容被压入堆栈,子程序从堆栈取得调用程序的代码段选择子,再从中分离出RPL就可得调用程序的执行特权级。
    5.装载任务状态段寄存器TR
    在任务内发生权变时堆栈也随着自动切换,外层堆栈指针保存在内层堆栈中,而内层堆栈指针存放在当前任务的TSS中,所以,在从外层向内层变换时,要访问TSS(从内层向外层转移不需要访问TSS,而只需内层栈中保存的栈指针)。实例在进入保护模式下的临时代码段后,通过如下两条指令装载任务状态段寄存器TR,使其指向已经预制好的任务TSS:
    mov ax,demoTSS_Sel
    ltr ax //装载任务状态段寄存器tr指令,操作数对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符,把TSS段描述符的基地址和界限等信息装入TR的高速缓冲寄存器中。
     
     
     
     
     
    五,任务切换
    利用段间转移指令JMP或者段间调用指令CALL,通过任务门或直接通过任务状态段,可以切换到别的任务。此外,中断、异常或者执行IRET指令时也可能发生任务切换。需要注意的是,因为ret指令的目标地址只能使用代码段描述符,所以,不能通过ret指令实现任务切换。
    1.直接通过TSS进行任务切换
    段间转移指令JMP或段间调用指令CALL所含指针的选择子指示一个可用任务状态段TSS描述符时,正常情况下就发生从当前任务到由该可用TSS对应任务(目标任务)的切换。目标任务的入口点由目标任务TSS内的cs和eip字段所规定的指针确定。这样的jmp或call指令内的偏移被丢弃
    对于段间调用使用的call ,若目标选择子指示TSS段描述符或任务门时,则返回地址和外层栈指针并不压入堆栈。
    cpu采用与访问数据段相同的特权级规则控制对TSS段描述符的访问。TSS段描述符的DPL规定了访问该描述符的最外层级别,只有在相同级别或更内层级别的程序才可以访问它。同时,还要求指示它的选择子的RPL必须满足RPL<=TSS的DPL的条件。   
    当满足上述条件时,就开始任务切换。
    2.通过任务门进行任务切换。
    任务门内的选择子指示某个任务的TSS描述符。当段间转移指令JMP或段间调用指令CALL所含指针的选择子指示一个任务门时,正常情况下就发生任务切换,即从当前任务切换到由任务门内的选择子所指示的TSS描述符对应的任务(目标任务)。这样的JMP或CALL指令内的偏移被丢弃;任务门内的偏移没有意义。
    cpu采用与访问呢数据段相同的特权级规则控制对任务门的访问。任务门的DPL规定了访问该任务门的最外层特权级,只有在同级或更内层级别的程序才可以访问它。同时还要指示任务门的选择子RPL必须满足RPL<=任务门的DPL的条件。在这些条件满足时,再检查任务门内的选择子,要求该选择子指示GDT中的可用的TSS描述符。对于任务门所指向的TSS描述符的DPL不进行特权级检查。检查通过后,开始任务切换
    3.任务切换过程
    根据指示目标任务TSS描述符的选择子进行切换任务的过程如下:
    a 测试目标任务状态段的界限。TSS用于保存任务的各种状态信息,不同的任务,TSS中可以有数量不等的其他信息,根据任务状态段的基本格式,TSS的界限应该大于或等于103(104-1)
    b 把寄存器的现场保存到TSS 结构中,即当前的寄存器的值,eip是返回地址,指向引起任务切换指令的下一条指令,但是不把LDTR和CR3的内容保存到TSS中。
    c 将指示目标任务TSS的选择子装入TR寄存器中。同时把对应TSS的描述符装入TR的高速缓冲寄存器中。此后,当前任务改称为原任务,目标任务改称为当前任务
    d 恢复现场,根据TSS中的各个寄存器的内容  ,恢复各个寄存器 EFLAGS和EIP,在装入寄存器的过程中,为了能正确地处理可能发生的异常,只把对应选择子装入各段寄存器。此时选择子的p位为0,还装载CR3寄存器。
    e 进行链接处理。如果需要连接,就将指向原任务TSS的选择子写入当前任务TSS的链接字段,把当前任务TSS描述符类型改为忙 (注意不修改原任务状态段描述符的忙位),并经标志寄存器EFLAGS中的NT位置为1,表示是嵌套任务。
    如果需要解链   就把原任务TSS描述符类型改为可用,如果无解链处理,那么就将原任务TSS描述符类型置为可用,当前任务
    f 把cr0中的TS标志置为1,表示已经发生过任务切换,在当前任务使用协处理器  产生自陷,由自陷处理程序完成有关协处理器现场的保存和恢复,这有利于快速地进行任务切换。
    g 把TSS中的CS选择子的RPL作为当前任务特权级设置为CPL。又因为装入CS高速缓冲寄存器时,要检测CPL=代码段描述符的DPL,所以TSS中的选择子所指示的代码段描述符的DPL必须等于该选择子的RPL。任务切换可以在一个任务的任何特权级发生,并且可以切换到另一个任务的任何特权级。
    h 装载LDTR寄存器。一个任务可以有自己的LDT,也可以没有。当任务没有LDT时,TSS中LDT选择子为0.如果TSS中LDT选择子非空,则从GDT中读出对应的LDT描述符,在经过测试后,把所读的LDT描述符装入LDTR高速缓冲寄存器。如果LDT选择子为空,则将LDT的存在位置为0,表明任务不使用LDT
    i 装载cs ss和各数据段寄存器及其对应的高速缓冲寄存器。在装入代码段高速缓存之前,也要进行特权检查,cpu调整TSS中的CS选择子的RPL=0,装入之后,调整CS的RPL等于目标代码段的DPL。SS使用的是TSS中的SS和SP字段的值,而不是使用内层栈保存区中的指针,即使发生了向内层的变换。这与任务内的通过调用门的转移不同。
    j 把调试寄存器DR7中的局部启用位置为0,以清除局部于原任务的各个断点和方式。
     
     
    关于任务状态和嵌套的说明
    需要注意的是  任务切换不能递归
    在段间转移指令JMP引起任务切换时,不实施链接,不导致任务的嵌套。它要求目标任务是可用任务。切换过程中把原任务置为“可用”,目标任务置为“忙”
    在段间调用指令CALL引起任务切换时,实施链接,导致任务的桥套,它要求目标任务是可用的任务。在切换过程中把目标任务置为“忙”,原任务仍保持“忙”;标志寄存器EFLAGS中的NT位被置为1,表示任务是嵌套任务
    在由中断异常引起任务切换时,实施链接,导致任务的嵌套。要求目标任务是可用的任务。在切换过程中把目标任务置为“忙”,原任务仍保持“忙”;标志寄存器中的NT位被置为1,表示任务是嵌套任务
    在执行IRET指令时引起任务切换,那么实施解链。要求目标任务是忙的任务。在切换过程中把原任务置为“可用”,目标任务仍保持“忙”。关于中断/异常如何引起任务切换和指令IRET如何考虑任务切换的内容将在后面的文章中论述。
     
     
    实例   任务切换  实例五
    逻辑功能
    :切换后显示原任务的挂起点eip的值。该实例演示内容包括:直接通过TSS段的任务切换,通过任务门的任务切换,任务内
    权级变换及参数传递。
    1.实现步骤:为了达到演示任务切换和权级变换的目的,在保护方式下涉及到两个任务,
    一个任务临时任务,另一个为演示任务。功能是演示通过调用门调用实现权级的变换和堆栈间参数的自动复制,
    临时任务和演示任务配合展示任务切换.
    a 实模式下初始化
    b 切换到保护模式
    c 设置TR 对应为临时任务,权级为0
    d 直接切换到演示任务,演示任务的权级为2
    e 把入口参数压入堆栈,经过调用门进入显示信息子程序,显示信息子程序的权级为0
    f 从堆栈中取出入口参数并处理
    g 从现实子程序返回权级为2的演示代码段;
    h 准备切换回实模式部分工作
    i 经过任务门切换到权级为0的临时任务
    j 准备返回实模式
    k 切换回实模式
    l 实模式下的回复
    2.源程序组织和清单
    a gdt 包括demotss   ldt   temptss   temptss_code    sub_code    normal    videobuffer
    b demotss  初始化
    c demotss的LDT段   包含   temptss_stack0   和temptss_stack2   temp_code   temp_data     gate
    d demotss_stack0  demotss_stack2   32位段  权级为0和2
    e 演示任务数据段   32位   权级为3
    f 子程序代码段 32位 权级为0
    g demotss_code  32位   权级为2
    h temptss      未初始化
    i temptss_code   16位代码段   权级为0
    j 实模式下的数据和代码段
    切换后显示原任务的挂起点eip的值  
    ;名称:ASM5.ASM
    ;功能:演示任务切换和任务内有特权级变换的转移
    ;编译:TASM ASM5.ASM
    ;链接:TLINK /32 ASM5.OBJ
    ;----------------------------------------------------------------------------
    INCLUDE         386SCD.INC
    ;----------------------------------------------------------------------------
    GDTSeg          SEGMENTPARAUSE16;全局描述符表数据段(16位);----------------------------------------------------------------------------;全局描述符表
    GDT             LABELBYTE;空描述符
    DUMMY           Desc    <>					;规范段描述符及选择子
    Normal          Desc    <0ffffh,,,ATDW,,>
    Normal_Sel      =       Normal-GDT
                  					;视频缓冲区段描述符(DPL=3)及选择子
    VideoBuf        Desc    <0ffffh,8000h,0bh,ATDW+DPL3,,>
    Video_Sel       =       VideoBuf-GDT
    ;----------------------------------------------------------------------------
    EFFGDT          LABELBYTE				;演示任务的局部描述符表段的描述符及选择子
    DemoLDTab       Desc    <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
    DemoLDT_Sel     =       DemoLDTab-GDT
                    			;演示任务的任务状态段描述符及选择子
    DemoTSS         Desc    <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
    DemoTSS_Sel     =       DemoTSS-GDT
                    			;临时任务的任务状态段描述符及选择子
    TempTSS         Desc    <TempTSSLen-1,TempTSSSeg,,AT386TSS+DPL2,,>
    TempTSS_Sel     =       TempTSS-GDT
                    			;临时代码段描述符及选择子
    TempCode        Desc    <0ffffh,TempCodeSeg,,ATCE,,>
    TempCode_Sel    =       TempCode-GDT
                   				;子程序代码段描述符及选择子
    SubR            Desc    <SubRLen-1,SubRSeg,,ATCE,D32,>
    SubR_Sel        =       SubR-GDT
    ;----------------------------------------------------------------------------
    GDNum           =($-EFFGDT)/(SIZE Desc);需处理基地址的描述符个数   $指什么   gdnum变量位置 还是gdt表中
    			描述符分配指示的下一个位置  后面的说明似乎说的通
    GDTLen          =$-GDT                     ;全局描述符表长度;----------------------------------------------------------------------------
    GDTSeg          ENDS;全局描述符表段定义结束
    ;----------------------------------------------------------------------------
    DemoLDTSeg      SEGMENTPARAUSE16;局部描述符表数据段(16位);----------------------------------------------------------------------------
    DemoLDT         LABELBYTE;局部描述符表			//演示任务用到的ldt;0级堆栈段描述符(32位段)及选择子
    DemoStack0      Desc    <DemoStack0Len-1,DemoStack0Seg,,ATDW,D32,>		//演示任务的两个堆栈
    DemoStack0_Sel  =       DemoStack0-DemoLDT+TIL
                    ;2级堆栈段描述符(32位段)及选择子
    DemoStack2      Desc    <DemoStack2Len-1,DemoStack2Seg,,ATDW+DPL2,D32,>
    DemoStack2_Sel  =       DemoStack2-DemoLDT+TIL+RPL2
                    ;演示任务代码段描述符(32位段,DPL=2)及选择子
    DemoCode        Desc    <DemoCodeLen-1,DemoCodeSeg,,ATCE+DPL2,D32,>		//演示任务的代码
    DemoCode_Sel    =       DemoCode-DemoLDT+TIL+RPL2
                    ;演示任务数据段描述符(32位段,DPL=3)及选择子
    DemoData        Desc    <DemoDataLen-1,DemoDataSeg,,ATDW+DPL3,D32,>		//演示任务的数据	
    DemoData_Sel    =       DemoData-DemoLDT+TIL		;把LDT作为普通数据段描述的描述符(DPL=2)及选择子
    ToDLDT          Desc    <DemoLDTLen-1,DemoLDTSeg,,ATDW+DPL2,,>		//ldt表作为普通数据段的别名
    ToDLDT_Sel      =       ToDLDT-DemoLDT+TIL
                    ;把TSS作为普通数据段描述的描述符(DPL=2)及选择子
    ToTTSS          Desc    <TempTSSLen-1,TempTSSSeg,,ATDW+DPL2,,>		//临时任务作为普通数据段的别名
    ToTTSS_Sel      =       ToTTSS-DemoLDT+TIL
    ;----------------------------------------------------------------------------
    DemoLDNum       =($-DemoLDT)/(SIZE Desc);需处理基地址的LDT描述符数;----------------------------------------------------------------------------;指向子程序SubRB代码段的调用门(DPL=3)及选择子
    ToSubR          Gate    <SubRB,SubR_Sel,,AT386CGate+DPL3,>		//两个门
    ToSubR_Sel      =       ToSubR-DemoLDT+TIL+RPL2
                    ;指向临时任务Temp的任务门(DPL=3)及选择子		//任务门 选择子指向任务
    ToTempT         Gate    <,TempTSS_Sel,,ATTaskGate+DPL3,>
    ToTempT_Sel     =       ToTempT-DemoLDT+TIL
    ;----------------------------------------------------------------------------
    DemoLDTLen      =$-DemoLDT
    ;----------------------------------------------------------------------------
    DemoLDTSeg      ENDS;局部描述符表段定义结束
    ;----------------------------------------------------------------------------
    DemoTSSSeg      SEGMENT PARA USE16                ;任务状态段TSS
    ;----------------------------------------------------------------------------
                    DD      0                         ;链接字
                    DD      DemoStack0Len             ;0级堆栈指针
                    DW      DemoStack0_Sel,0          ;0级堆栈选择子
                    DD      0                         ;1级堆栈指针(实例不使用)
                    DW      0,0                       ;1级堆栈选择子(实例不使用)
                    DD      0                         ;2级堆栈指针
                    DW      0,0                       ;2级堆栈选择子
                    DD      0                         ;CR3
                    DW      DemoBegin,0               ;EIP
                    DD      0                         ;EFLAGS
                    DD      0                         ;EAX
                    DD      0                         ;ECX
                    DD      0                         ;EDX
                    DD      0                         ;EBX
                    DD      DemoStack2Len             ;ESP
                    DD      0                         ;EBP
                    DD      0                         ;ESI
                    DD      1986                      ;EDI
                    DW      Video_Sel,0               ;ES
                    DW      DemoCode_Sel,0            ;CS
                    DW      DemoStack2_Sel,0          ;SS
                    DW      DemoData_Sel,0            ;DS
                    DW      ToDLDT_Sel,0              ;FS
                    DW      ToTTSS_Sel,0              ;GS
                    DW      DemoLDT_Sel,0             ;LDTR
                    DW      0                         ;调试陷阱标志
                    DW      $+2                       ;指向I/O许可位图
                    DB      0ffh                      ;I/O许可位图结束标志
    DemoTSSLen      =       $
    ;----------------------------------------------------------------------------
    DemoTSSSeg      ENDS                              ;任务状态段TSS结束
    ;----------------------------------------------------------------------------
    DemoStack0Seg   SEGMENT PARA USE32                ;演示任务0级堆栈段(32位段)
    DemoStack0Len   =       1024
                    DB      DemoStack0Len DUP(0)
    DemoStack0Seg   ENDS                              ;演示任务0级堆栈段结束
    ;----------------------------------------------------------------------------
    DemoStack2Seg   SEGMENT PARA USE32               ;演示任务2级堆栈段(32位段)
    DemoStack2Len   =       512
                    DB      DemoStack2Len DUP(0)
    DemoStack2Seg   ENDS                              ;演示任务2级堆栈段结束
    ;----------------------------------------------------------------------------
    DemoDataSeg     SEGMENT PARA USE32                ;演示任务数据段(32位段)
    Message         DB      'Value=',0
    DemoDataLen     =       $
    DemoDataSeg     ENDS                              ;演示任务数据段结束
    ;----------------------------------------------------------------------------
    SubRSeg         SEGMENT PARA USE32                ;子程序代码段(32位)
                    ASSUME  CS:SubRSeg
    ;----------------------------------------------------------------------------
    SubRB           PROC    FAR
                    push    ebp
                    mov     ebp,esp
                    pushad                            ;保护现场
                    mov     esi,DWORD PTR [ebp+12]    ;从0级栈中取出显示串偏移
                    mov     ah,4ah                    ;设置显示属性
                    jmp     SHORT SubR2
    SubR1:          stosw
    SubR2:          lodsb
                    or      al,al
                    jnz     SubR1
                    mov     ah,4eh                    ;设置显示属性
                    mov     edx,DWORD PTR [ebp+16]    ;从0级栈中取出显示值
                    mov     ecx,8
    SubR3:          rol     edx,4
                    mov     al,dl
                    call    HToASCII
                    stosw
                    loop    SubR3
                    popad
                    pop     ebp
                    ret     8
    SubRB           ENDP
    ;----------------------------------------------------------------------------
    HToASCII        PROC
                    and     al,0fh
                    add     al,90h
                    daa
                    adc     al,40h
                    daa
                    ret
    HToASCII        ENDP
    ;----------------------------------------------------------------------------
    SubRLen         =       $
    SubRSeg         ENDS                              ;子程序代码段结束
    ;----------------------------------------------------------------------------
    DemoCodeSeg     SEGMENT PARA USE32                ;演示任务的32位代码段
                    ASSUME  CS:DemoCodeSeg,DS:DemoDataSeg
    ;----------------------------------------------------------------------------
    DemoBegin       PROC    FAR
                    ;把要复制的参数个数置入调用门
                    mov     BYTE PTR fs:ToSubR.DCount,2
                    ;向2级堆栈中压入参数
                    push    DWORD PTR gs:TempTask.TREIP
                    push    OFFSET Message
                    ;通过调用门调用SubRB
                    CALL32  ToSubR_Sel,0
                    ;把指向规范数据段描述符的选择子填入临时任务TSS
                    ASSUME  DS:TempTSSSeg
                    push    gs
                    pop     ds
                    mov     ax,Normal_Sel
                    mov     WORD PTR TempTask.TRDS,ax
                    mov     WORD PTR TempTask.TRES,ax
                    mov     WORD PTR TempTask.TRFS,ax
                    mov     WORD PTR TempTask.TRGS,ax
                    mov     WORD PTR TempTask.TRSS,ax
                    ;通过任务门切换到临时任务
                    JUMP32  ToTempT_Sel,0
                    jmp     DemoBegin
    DemoBegin       ENDP
    DemoCodeLen     =       $
    ;----------------------------------------------------------------------------
    DemoCodeSeg     ENDS                              ;演示任务的32位代码段结束
    ;----------------------------------------------------------------------------
    TempTSSSeg      SEGMENT PARA USE16                ;临时任务的任务状态段TSS
    TempTask        TSS     <>
                    DB      0ffh                      ;I/O许可位图结束标志
    TempTSSLen      =       $
    TempTSSSeg      ENDS
    ;----------------------------------------------------------------------------
    TempCodeSeg     SEGMENTPARAUSE16;临时任务的代码段ASSUMECS:TempCodeSeg
    ;----------------------------------------------------------------------------
    Virtual         PROCFARmovax,TempTSS_Sel            ;装载TRltrax JUMP16  DemoTSS_Sel,0   ;直接切换到演示任务clts;清任务切换标志moveax,cr0;准备返回实模式andal,11111110bmovcr0,eaxJUMP16<SEG Real>,<OFFSET Real>
    Virtual         ENDP;----------------------------------------------------------------------------
    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,ES: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    QWORD 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
    ;----------------------------------------------------------------------------
    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
    ;----------------------------------------------------------------------------
    ;入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
    ;----------------------------------------------------------------------------
    InitLDT         PROC
                    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    InitLDT
                    ret
    InitLDT         ENDP
    ;----------------------------------------------------------------------------
    RCodeSeg        ENDS
                    END     Start
    
    关于实例五的说明
    下面主要就任务切换和通过调用门实现任务内特权级变换时参数的复制等情形做些说明
    1.从临时任务直接通过TSS切换到演示任务   任务间的切换
    a 实模式到保护模式后,进入临时任务
    b ltr指令将指向temptss的选择子装入tr   此时tss中的内容并未初始化
    c 保存临时任务的现场到temptss中   
    d temptss到demotss转移使用JMP  执行切换时,cpl=0 ,jmp 指定的选择子中的rpl=0,demotss的描述符权级dpl=0 并且tss类型为可用
    切换过程:
    a temptss的执行现场保存到临时任务的TSS中;  寄存器值到tss结构中
    b demotss结构中的数值恢复到对应的寄存器
    c demotss的选择子入 LDTR寄存器
    demotss中的cs字段存放的是选择子democode_sel,对应的描述符在demotss的LDT中,并且DPL=2,它描述了代码段democode;挂起点为demobegin(偏移为位置),并且cpl=2  
    命令jmp不实施任务链接。
    2.从演示任务通过任务门切换到temptss   任务间的切换
    使用段间转移JMP,通过任务门ToTempT切换到temptss。
    在执行切换到temptss的jmp时,cpl=2,JMP指令中所含的选择子ToTempT_Sel内的RPL=0,它只是的任务门的描述符权级DPL=3,所以可以访问该任务门。
    任务门的选择子指向temptss   且此时的temptss是可用的,所以可以顺利进行任务切换。demotss的现场保存的demotss的结构中;temptss对应的各个寄存器的值从temptss中恢复。
    temptss的挂起点是temptss代码段的ToRela点?,所以恢复后的temptss从该点开始,cs含有temptss代码段的选择子。但是由于在demotss内“强硬”地改变了temptss内的ss和ds等字段,所以在恢复到temptss任务时,ss和ds等段寄存器内已经含有规范数据段的选择子,而不是挂起时的原有值。
    3.demotss内的权级变换和堆栈传递参数  涉及到演示任务内的不同段
    demotss采用段间调用指令CALL,通过调用门ToSubR调用子程序SubRB。执行段间调用指令CALL时的cpl=2,指令所含指向调用门的选择子的RPL=2,调用门的DPL=3,所以对调用门的访问时允许的
    尽管调用门内的选择子的RPL=3,但由于它所指示的子程序代码段描述符DPL=0,所以在调用过程中就发生了从权级2到权级0的变换,同时堆栈也被切换。
    demotss通过堆栈传递了两个参数给子程序SubRB   把参数压入堆栈时,当前cpl=2,使用的也是对应权级2的堆栈。通过调用门进入子程序后,cpl=0,使用0级堆栈。为此,把调用门ToSubR中的DCount字段设置为2,表示权级向内层变换时,需要从外层堆栈依次复制2个双字参数到内层堆栈。随着权级变换,堆栈也跟着变换。这种在堆栈切换的同时复制所需参数的做法,保证了子程序方便地访问堆栈中的参数,而无需考虑是哪个堆栈。
    从子程序subrb返回,当前为cpl=0  变换为cpl=2,堆栈也回到2级堆栈。由于再次进入0级堆栈,总是从空开始,所以返回前不是非要保持内层堆栈平衡不可(什么意思)。但是2级堆栈中的2个双字参数需要废除,从源程序可见,这是采用带立即数的段间返回指令实现的,在返回的同时,自动废除外层堆栈中的参数,同时也废除了内层堆栈中的参数。也就是跳过内层堆栈传递给外层堆栈的参数  涉及到esp的指针的操作。
    4.别名技术的应用
    为了把调用们ToSubR中的DCount字段设置成2,使用一个数据段描述符ToDLDT描述符调用门所在演示任务的LDT段,该描述符把演示任务的LDT段描述符成数据段。
    还有一处是把temptss段视为普通数据段。从demotss切换到temptss之前,把指向描述符规范数据段的描述符normal的选择子normal_sel填到temptss中的各数据段寄存器,于是在切换到temptss时,作为恢复temptss的现场,该选择子就被装到ds等数据段寄存器,对应的描述符normal内的信息也就被装入到对应的高速缓冲寄存器中,达到为从临时任务切换到实模式作准备的目的。
     
     
  • 相关阅读:
    POJ_1523 SPF (Tarjan 求割点)
    POJ 3177&& 3352
    POJ 基础数据结构
    Bellman Ford, SPFA 学习笔记(含有负权的单源最短路径)
    HDU_3062 Party (2SAT)
    POJ二分图最大匹配的简单题目
    POJ 2553 The Bottom of a Graph (Trajan 强连通分量 缩点)
    POJ_3678 Katu Puzzle (2SAT)
    HDU_3836 Equivalent Set (Trajan 强连通分量 缩点)
    POJ1904 King's Quest(Tarjan 求缩点)
  • 原文地址:https://www.cnblogs.com/dongguolei/p/7896466.html
Copyright © 2020-2023  润新知