控制转移可以分为两大类 :同一任务内的控制转移 和 任务间的控制转移(任务切换)
同一个任务内的控制转移可以分为段内转移 、特权级不变的段间转移和特权级改变的段间转移
段内转移与实模式相同 不涉及特权级变换和任务切换
只有段间转移才涉及特权级变换和任务切换任务 重点为任务内的特权级变换和任务间的切换
一 任务内无特权级变换的转移
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内的信息也就被装入到对应的高速缓冲寄存器中,达到为从临时任务切换到实模式作准备的目的。