控制转移.docx
《控制转移.docx》由会员分享,可在线阅读,更多相关《控制转移.docx(55页珍藏版)》请在冰豆网上搜索。
控制转移
六.控制转移
控制转移基本上可分为两大类:
同一任务内的控制转移和任务间的控制转移(任务切换)。
同一任务内的控制转移又可分为:
段内转移、特权级不变的段间转移和特权级变换的段间转移。
段内转移与实模式下相似,不涉及特权级变换和任务切换。
只有段间转移才涉及特权级变换和任务切换。
本文介绍保护方式下的控制转移,重点是任务内的特权级变换和任务间的切换。
这里下载本文所有源代码。
<一>任务内无特权级变换的转移
各种段内转移与实模式下相似,当然不涉及特权级变换和任务切换。
只有各种形式的段间转移才涉及特权级变换和任务切换。
1.段间转移指令
与实模式下相同,指令JMP、CALL和RET都具有段间转移的功能,指令INT和IRET总是段间转移。
此外,中断/异常也将引起段间转移。
有时把这些具有段间转移功能的指令统称为段间转移指令。
在保护模式下,段间转移的目标位置由选择子和偏移构成的地址表示,常把它称为目标地址指针。
在32位代码段中,上述指针内的偏移使用32位表示,这样的指针也称为48位全指针。
在实例二的32位代码段内就使用了48位全指针。
在16位代码段中,上述指针内的偏移只使用16位表示。
与实模式下相似,段间转移指令JMP和段间调用指令CALL还可分为段间直接转移和段间间接转移两类。
如果指令JMP和CALL在指令中直接含有目标地址指针,那么就是段间直接转移;如果指令中含有指向包含目标地址指针的门描述符或TSS描述符的指针,那么就是段间间接转移,这种指针只有选择子部分有效,指示调用门、任务门或TSS描述符,而偏移部分不起作用。
实际上,当段间转移指令JMP和段间调用指令CALL所含指针的选择子部分指示代码段描述符,那么就是段间直接转移,偏移部分表示目标代码段的入口点;当选择子部分指示门描述符或TSS描述符时,就是段间间接转移。
2.向目标代码段转移的步骤
处理器在执行上述段间转移指令向目标代码段实施转移的过程中,一般至少要经过如下步骤:
(1)判断目标地址指针内的选择子指示的描述符是否为空描述符。
空描述符是GDT中的第0个描述符,是一个特殊的描述符。
目标代码段描述符不能为空描述符,也即选择子的高14位不能为0。
(2)从全局或局部描述符表内读出目标代码段描述符。
由选择子内的TI位,确定使用全局描述符表还是局部描述符表。
(3)根据情况,检测描述符类型是否正确;调整RPL。
(4)把目标代码段描述符内的有关内容装载到CS高速缓冲寄存器。
(5)判断目标地址指针内的偏移是否越出代码段的界限。
目标地址指针内的偏移必须不超过目标代码段界限。
(6)装载CS段寄存器和指令指针寄存器EIP;CPL存入CS内选择子的RPL字段。
上述步骤只是对转移过程的简单说明,实际的动作还要复杂。
在把目标代码段描述符内的有关内容转载到CS高速缓冲寄存器时,还要进行如下保护检测,其中的DPL表示目标代码段描述符的特权级:
(1)对于非一致代码段,要求CPL=DPL,RPL<=DPL;对于一致代码段,要求CPL>=DPL。
(2)代码段必须存在,即描述符中的P位必须是1。
通常描述符特权级DPL规定了对应段的特权级。
如果描述符描述的是数据段,那么DPL就规定了访问该数据段的最外层特权级;如果描述符描述的是代码段,那么DPL就规定了执行该代码段所需要的CPL。
但从上述装载CS高速缓冲寄存器时进行的保护检测可见,对于一致代码段,却要求CPL>=DPL,也就是说,一致代码段描述符中的DPL规定了可以转移到一致代码段的最内层特权级。
于是,3级的程序可以转移到任何一致的代码段,而0级的程序只允许转移到DPL等于0的一致代码段。
一致代码段描述符内DPL的这种解释,正好与正常的DPL的解释相反。
一致的可执行段是一种特别的段。
这种存储段,为在多个特权级执行的程序,提供对子例程的共享支持,而不要求改变特权级。
例如,通过把数值库例程放在一致的代码段中,可以使不同级执行的程序共享数值库例程。
这样,任何特权级的程序可以使用段间调用指令,调用库中的例程,并在调用者所具有的特权级执行该例程。
3.任务内无特权级变换的转移
所谓任务内无特权级变换的转移指:
在转移到新的代码段时,当前特权级CPL保持不变。
利用段间转移指令JMP、段间调用指令CALL和段间返回指令RET可实现任务内无特权级变换的转移。
利用INT指令和IRET指令也可实现任务内无特权级变换的转移。
(1)利用段间直接转移指令JMP或CALL
在执行段间转移指令JMP时,如果指令内所含指针指示一个代码段,那么就直接开始上述向目标代码段转移的步骤;在执行段间调用指令CALL时,如果指令内所含指针指针指示一个代码段,那么就把返回地址指针压栈,然后就直接开始上述向目标代码段转移的步骤。
顺利通过这几步(不调整RPL)后,就完成了任务内无特权级变换的转移。
由此可见,利用段间直接转移指令JMP或调用指令CALL可方便地进行任务内无特权级变换的转移,但不能进行任务内特权级变换的转移。
(2)利用段间返回指令RET
在执行段间返回指令RET时,如果从堆栈中弹出的目标地址指针指示一个代码段,并且选择子符合RPL=CPL的条件,那么就开始上述向目标代码段的转移步骤。
顺利通过这几步后,就完成了任务内无特权级变换的转移。
通常情况下,段间返回指令RET与段间调用指令CALL对应。
在利用段间调用指令CALL以任务内无特权级变换的方式转移到某个子程序后,在子程序内利用段间返回指令RET以任务内无特权级变换的方式返回主程序。
由于调用时无特权级变换,所以返回时也无特权级变换,如果真是如此,那么必须能够满足条件RPL=CPL。
(3)利用调用门和其它途径
如何利用调用门实行和其它方法实现任务内无特权级变换的转移将在后面的文章中介绍。
4.装载数据段和堆栈段寄存器时的特权检测
上面简单地说明了把选择子装入代码段寄存器CS时为实现保护而进行的检测,下面也简单地说明在把选择子装入数据段寄存器和堆栈段寄存器时要进行的检测。
在把选择子装入数据段寄存器DS、ES、FS或GS时,要进行如下检测:
(1)选择子不能为空;
(2)选择子指定的描述符必须是数据段描述符、可读可执行的代码段或一致可读的可执行代码段的描述符;
(3)对于数据段和可读可执行代码段,要求CPL<=DPL,RPL<=DPL;
(4)对应的段必须存在。
若装入的选择子不满足上述要求,则会产生异常。
在把选择子装入堆栈段寄存器SS时要进行如下检测:
(1)选择子不能为空;
(2)选择子指定的描述符必须是可读写的数据段描述符;
(3)要求CPL=DPL=RPL;
(4)对应段必须存在。
若装入的选择子不满足上述条件,则在装入SS时就会引起异常。
<二>演示任务内无特权级变换转移的实例(实例三)
在实例二中,32位代码段到16位代码段的转移就是任务内无特权级转移的例子。
下面再给出一个用于演示任务内无特权级变换转移的实例。
该实例使用了段间转移指令JMP、段间调用指令CALL和段间返回指令RET实现同一任务内相同特权级的转移。
该实例还建立并使用了局部描述符表LDT。
1.实现步骤和源程序
实现步骤如下:
(1)实模式下的初始化,包括对GDT和演示任务LDT的初始化,装载GDTR;
(2)从实模式切换到保护模式,处于0特权级;(3)装载LDTR,并设置堆栈;(4)利用段间转移指令JMP实现从代码段K到同级代码段L的转移;(5)利用段间调用指令CALL调用同级代码段C中的子程序D显示字符串信息;(6)利用段间调用指令CALL调用同级代码段C中的子程序H把十六进制数转换成对应的ASCII码;(7)再利用段间调用指令CALL调用同级代码段C中的子程序D显示字符串信息;(8)利用段间转移指令JMP实现从代码段L到代码段K的转移;(9)从保护模式切换到实模式;(10)在实模式下结束程序。
该实例的逻辑功能是用十六进制数的形式显示代码段L的段界限的值。
源程序如下:
;名称:
ASM3.ASM
;功能:
演示任务内无特权级变换的转移
;编译:
TASMASM3.ASM
;连接:
TLINKASM3.OBJ
;----------------------------------------------------------------------------
INCLUDE386SCD.INC
;----------------------------------------------------------------------------
GDTSegSEGMENTPARAUSE16'GDT';全局描述符表数据段(16位)
;----------------------------------------------------------------------------
GDTLABELBYTE;全局描述符表
DUMMYDesc<>;空描述符
NormalDesc<0ffffh,,,ATDW,,>;规范段描述符
CodeKDesc<0ffffh,,,ATCE,,>;代码段K的描述符
LDTableDesc;局部描述符表段的描述符
;----------------------------------------------------------------------------
GDTLen=$-GDT;全局描述符表长度
;----------------------------------------------------------------------------
Normal_Sel=Normal-GDT;规范段描述符选择子
CodeK_Sel=CodeK-GDT;代码段K的选择子
LDT_Sel=LDTable-GDT;局部描述符表段的选择子
;----------------------------------------------------------------------------
GDTSegENDS;全局描述符表段定义结束
;----------------------------------------------------------------------------
LDTSegSEGMENTPARAUSE16'LDT';局部描述符表数据段(16位)
LDTLABELBYTE;局部描述符表
;代码段L的描述符
CodeLDesc
;代码段C的描述符
CodeCDesc
;显示缓冲区段描述符
VideoBufDesc<0ffffh,0b800h,,ATDW,,>
;LDT别名段描述符(DPL=3)
ToLDTDesc
;显示信息缓冲区数据段描述符(DPL=3)
MDataDesc
;堆栈段描述符
StackSDesc
;----------------------------------------------------------------------------
LDTLen=$-LDT;LDT所占字节数
LDNum=($-LDT)/(SIZEDesc);LDT含描述符项数
;----------------------------------------------------------------------------
CodeL_Sel=CodeL-LDT+TIL;代码段L的选择子
CodeC_Sel=CodeC-LDT+TIL;代码段C的选择子
Video_Sel=VideoBuf-LDT+TIL;显示缓冲区选择子
ToLDT_Sel=ToLDT-LDT+TIL;LDT别名段选择子
MData_Sel=MData-LDT+TIL+RPL3;显示信息数据段选择子
Stack_Sel=StackS-LDT+TIL;堆栈段选择子
;----------------------------------------------------------------------------
LDTSegENDS;局部描述符表段定义结束
;----------------------------------------------------------------------------
MDataSegSEGMENTPARAUSE16'MDATA';显示信息缓冲区数据段
;----------------------------------------------------------------------------
MessageDB'Value=',0
BufferDB80DUP(0)
MDataLen=$
;----------------------------------------------------------------------------
MDataSegENDS;显示缓冲区数据段结束
;----------------------------------------------------------------------------
StackSegSEGMENTDWORDUSE16'STACK';堆栈段
;----------------------------------------------------------------------------
DW512DUP(?
)
TopOfS=$
;----------------------------------------------------------------------------
StackSegENDS;堆栈段结束
;----------------------------------------------------------------------------
CodeCSegSEGMENTPARAUSE16'CODEC';任务代码段C
ASSUMECS:
CodeCSeg
;----------------------------------------------------------------------------
;显示信息子程序
;入口参数:
fs:
si指向要显示的以0结尾的字符串,es:
di指向显示缓冲区
;----------------------------------------------------------------------------
DispMsgPROCFAR
movah,01001110b
Disp1:
moval,BYTEPTRfs:
[si]
incsi
oral,al
jzDisp2
movWORDPTRes:
[di],ax
incdi
incdi
jmpDisp1
Disp2:
ret
DispMsgENDP
;----------------------------------------------------------------------------
;把AL寄存器低4位二进制数(一位16进制数)转换成ASCII码
;----------------------------------------------------------------------------
HToASCIIPROCFAR
andal,00001111b
addal,90h
daa
adcal,40h
daa
ret
HToASCIIENDP
;----------------------------------------------------------------------------
CodeCLen=$
;----------------------------------------------------------------------------
CodeCSegENDS;代码段C定义结束
;----------------------------------------------------------------------------
CodeLSegSEGMENTPARAUSE16'CODEL'
ASSUMECS:
CodeLSeg
;----------------------------------------------------------------------------
Virtual2PROCFAR
movax,Video_Sel;设置显示缓冲区指针
moves,ax
movdi,1986
movax,MData_Sel;设置提示信息缓冲区指针
movfs,ax
movsi,OFFSETMessage
CALL16CodeC_Sel,DispMsg;显示提示信息
movax,ToLDT_Sel;把演示任务的LDT的别名
movgs,ax;段的描述符选择子装入GS
movdx,WORDPTRgs:
CodeL.LimitL
movsi,OFFSETBuffer;取代码段L的段界限值
movcx,4;并转成对应可显示字符串
Vir:
roldx,4
moval,dl
CALL16CodeC_Sel,HToASCII
movBYTEPTRfs:
[si],al
incsi
loopVir
movWORDPTRfs:
[si],'H'
movsi,OFFSETBuffer
CALL16CodeC_Sel,DispMsg
JUMP16CodeK_Sel,Virtual3
CodeLLen=$
Virtual2ENDP
;----------------------------------------------------------------------------
CodeLSegENDS
;----------------------------------------------------------------------------
CodeKSegSEGMENTPARAUSE16'CODEK'
ASSUMECS:
CodeKSeg
;----------------------------------------------------------------------------
Virtual1PROCFAR
movax,LDT_Sel
LLDTax;加载局部描述符表寄存器LDTR
movax,Stack_Sel
movss,ax;建立演示任务堆栈
movsp,OFFSETTopOfS
JUMP16CodeL_Sel,Virtual2
Virtual3:
movax,Normal_Sel
moves,ax
movfs,ax
movgs,ax
movss,ax
moveax,cr0
andal,11111110b
movcr0,eax
JUMP16,
CodeKLen=$
Virtual1ENDP
;----------------------------------------------------------------------------
CodeKSegENDS
;============================================================================
RDataSegSEGMENTPARAUSE16;实方式数据段
VGDTRPDesc;GDT伪描述符
SPVarDW?
;用于保存实方式下的SP
SSVarDW?
;用于保存实方式下的SS
RDataSegENDS
;----------------------------------------------------------------------------
RCodeSegSEGMENTPARAUSE16
ASSUMECS:
RCodeSeg
;----------------------------------------------------------------------------
StartPROC
ASSUMEDS:
GDTSeg
;-----------------
movax,GDTSeg
movds,ax
;初始化全局描述符表
movbx,16
movax,CodeKSeg
mulbx
movCodeK.BaseL,ax
movCodeK.BaseM,dl
movCodeK.BaseH,dh
movax,LDTSeg
mulbx
movLDTable.BaseL,ax
movLDTable.BaseM,dl
movLDTable.BaseH,dh
;设置GDT伪描述符
ASSUMEDS:
RDataSeg
movax,RDataSeg
movds,ax
movax,GDTSeg
mulbx
movWORDPTRVGDTR.Base,ax
movWORDPTRVGDTR.Base+2,dx
;初始化演示任务LDT
cld
callInit_MLDT
;保存实方式堆栈指针
movSSVar,ss
movSPVar,sp
;装载GDTR
lgdtQWORDPTRVGDTR
cli
;切换到保护方式
moveax,cr0
oral,1
movcr0,eax
JUMP16,
Real:
;又回到实方式
movax,RDataSeg
movds,ax
lsssp,DWORDPTRSPVar
sti
movax,4c00h
int21h
StartENDP
;----------------------------------------------------------------------------
Init_MLDTPROC
pushds
movax,LDTSeg
movds,ax
movcx,LDNum
movsi,OFFSETLDT
InitL:
movax,[si].BaseL