《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护相关的知识,希望对你有一定的参考价值。
1、 通常只在操作系统代码中使用,80386支持4个特权等级,操作系统指令也可分3种:实模式和任何特权级下可执行指令、实模式及特权级0下可执行的指令和仅在保护模式下执行的指令。
1) 实模式和任何特权级下可执行的指令
a)存储全局和中断描述符表寄存器指令
GDT与IDT整个系统各只有一张,它们的定位信息分别保存在GDTR与IDTR中,这两个寄存器的值可以被保存。须注意,LDT表示任务私有,存储LDTR值的指令不属于这一类。
i)存储全局描述符表寄存器指令:SGDT DST
DST是48位(6字节)存储器操作数,执行后GDTR的16位界限值存入DST低字,而GDTR中32位基地址存入DST高双字。对标志位无影响。
ii)存储中断描述符表寄存器指令:SIDT DST
与“SGDT DST”类似。
b)存储机器状态字指令:SMSW DST
DST可以是16位通用寄存器或存储单元。对标志位无影响。
说明:为了兼容80286指令集(386的CR0低字节等同于286机器状态字),在386中,存储机器字应该使用存储CR0寄存器指令。
2) 实模式及特权级0下可执行的指令
关键寄存器的设置指令等,在保护模式下CPL不为0的指令执行它们将引发错误码为0的通用保护故障。虚拟8086模式下(CPL = 3)同样如此。
a)清任务切换标志指令:CLTS
任务切换时,CR0的TS位(任务切换标志位)自动被置1(参考“《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移”),该指令功能是把TS标志位清0。不影响其它标志位。
b)暂停指令:HLT
该指令使处理器暂停执行,只有在接受一个已经启用的中断,或者让系统复位,才重新启动。对标志位没有影响。
c)装载全局描述符表寄存器指令:LGDT SRC
SRC是48位(6字节)存储器操作数,执行后伪描述符SRC(PDESC结构)的低字送GDTR低字(段界限),高双字送GDTR高双字(段基址)。对标志位无影响。
d)装载中断描述符表寄存器指令:LIDT SRC
与“LGDTR”类似。
e)装载机器状态字指令:LMSW SRC
SRC可以是16位通用寄存器或存储单元。该指令将SRC装入机器状态字(CR0的低16位),不影响标志位。同样,是为了兼容286,386不应该用它。
f)控制寄存器数据传送指令:MOV DST,SRC
实现386控制寄存器和32位通用寄存器之间的数据传送。所以,DST和SRC可以是3个控制寄存器(CR0、CR2、CR3---参考“《80X86汇编语言程序设计教程》八 80386程序设计基础”)和任一32位通用寄存器,但不能同时为控制寄存器。对标志位门影响。
g)调试寄存器数据传送指令
规则同上。调试寄存器为DR0~DR7(参考“《80X86汇编语言程序设计教程》八 80386程序设计基础”)。
h)测试寄存器数据传送指令
规则同上。测试寄存器为TR6和TR7(参考“《80X86汇编语言程序设计教程》八 80386程序设计基础”)。
3) 只能在保护模式下执行的指令
只能在保护模式下执行,在实模式下执行将引起非法操作码故障(向量号6)。
a)装载局部描述符表寄存器指令:LLDT SRC
SRC可以为16位通用寄存器或存储单元(代表的选择子必须指示GDT中类型为LDT的描述符,为0则表示空选择子,及不使用LDT),不影响标志位。CPL不为0时(错误码为0)、选择子不指示GDT中描述符或描述符类型不是LDT时( 错误码由该选择子构成),执行它将产生通用保护故障。
b)存储局部描述符表寄存器指令:SLDT DST
规则同上。
c)装载和存储任务寄存器指令
任务寄存器TR指示当前任务状态段TSS(参考“《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移”)。随着任务的切换而切换,如果任务嵌套,那么TR原值作为链接字保存在新任务TSS中。
i) 装载任务寄存器指令:LTR SRC
SRC为16位通用寄存器或存储单元。SRC指示的选择子不能为空,必须索引位于GDT中的描述符,且类型为TSS。该指令不影响标志位。CPL不为0时(错误码为0)、选择子不指示GDT中描述符或描述符类型不是LDT时(错误码由该选择子构成),执行它将产生通用保护故障。
ii)存储任务寄存器指令:STR DST
DST规则同上,不影响标志位。
d)调整申请特权级指令:ARPL OPRD1,OPRD2
OPRD1为16位通用寄存器或存储单元,OPRD2为16位通用寄存器。该指令用选择子OPRD2的申请特权级(RPL)去检查选择子OPRD1的RPL,如果OPRD1.RPL < OPRD2.RPL,那么ZF = 1,OPRD1.RPL = OPRD2.RPL;否则ZF = 0。二者都可为空,不影响其它标志位。
e)装载存取权指令:LAR OPRD1,OPRD2
两操作数都可以为16位或32位通用寄存器,OPRD2还可以是存储单元,但它们尺寸必须一样。如果选择子OPRD2(32位则使用低16位)所指示描述符满足下述条件,那么ZF = 1,并把描述符属性字段装入OPRD1;否则ZF = 0,OPRD1不变。
i)在描述符表范围内
ii)为存储段描述符或系统段描述符,或任务门、调用门描述符
iii)CPL和OPRD2.RPL都不大于OPRD2.DPL
描述符参考“《80X86汇编语言程序设计教程》九 分段管理机制及纯DOS环境搭建”与“《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移”。结果是高4字节与00FFXFF00相与的结果,X表示未定义,如果OPRD1为16位,那么只取结果低2字节(无G位、AVL位)。除ZF不影响其它标志位。
f)装载段界限指令:LSL OPRD1,OPRD2
规则同上,区别在于转载的是段界限字段且条件第二点限制更加严格,不能为门描述符。在满足条件下,装载到OPRD1的OPRD2所指示描述符的界限字段值以字节为单位。如果描述符中界限字段以4K为单位(G = 1),那么装入到OPRD1时左移12位,空出的位全部填1。如果使用16位操作数,只有段界限低16位被装载到OPRD1。除ZF不影响其它标志位。
g)读写检验指令
检查在当前特权级上指定的段是否能读写,从而避免不必要的异常。
i)读检验指令:VERR OPRD
OPRD可以是16位或32位通用寄存器和存储单元。如果为32位则使用低16位,功能是判断OPRD选择子指示的段在当前CPL是否可读,如果选择子合法,且在当前CPL可读,那么ZF置1,否则ZF清0。除ZF不影响其它标志位。
ii)写检验指令:VERW OPRD
规则同上,只不过检查的属性是是否可写。
4) 特权指令
保护模式下只有CPL = 0才能执行的指令,否则引发通用保护异常。特权指令在构造完善保护机制上起重要作用。总结如下:
指令 |
功能 |
指令 |
功能 |
CLTS |
清除CR0的TS位 |
LTR |
装入TR |
HLT |
停机 |
MOV CRn,reg |
装入控制寄存器 |
LGDT |
装入GDTR |
MOV reg,CRn |
保存控制寄存器 |
LIDT |
装入IDTR |
MOV DRn,reg |
装入测试寄存器 |
LLDT |
装入LDTR |
MOV reg,DRn |
保存测试寄存器 |
LMSW |
装入MSW(CR0低16位) |
|
|
可见,设置GDTR、IDTR和LDTR是特权指令,而存储它们不是。而设置和存储控制和测试寄存器都是特权指令。
2、 输入/输出保护
1) 输入/输出保护
a)I/O敏感指令
输入/输出特权级(I/O Privilege Level)规定可执行I/O有关指令和访问I/O空间地址的最外层特权级(在标志寄存器EFLAGS中,参考“《80X86汇编语言程序设计教程》八 80386程序设计基础”)。I/O许可位图(在TSS中)规定I/O空间哪些地址可以在任何特权级执行的代码访问。I/O敏感指令如下:
指令 |
功能 |
保护模式下执行条件 |
CLI |
清除EFLAGS中IF位 |
CPL <= IOPL |
STI |
设置EFLAGS中IF位 |
CPL <= IOPL |
IN |
从I/O地址读出数据 |
CPL <= IOPL或I/O位图允许 |
INS |
从I/O地址读出字符串 |
CPL <= IOPL或I/O位图允许 |
OUT |
从I/O地址写入数据 |
CPL <= IOPL或I/O位图允许 |
OUTS |
从I/O地址写入字符串 |
CPL <= IOPL或I/O位图允许 |
如果权限不够,则引发通用保护异常。每个任务有自己的EFLAGS与TSS,所以各个任务IOPL可以有不同,并且可定义不同的I/O许可位图。注意:实模式下全部可执行。
b)I/O许可位图
由二进制位串组成,每一位对应一个I/O地址,如果m位为0,那么I/O地址m可由本任何特权等级代码的程序访问,否则只能在IOPL特权级或更内层特权级执行的程序访问,不然引发通用保护异常。 一条I/O指令最多涉及4个I/O地址(如IN EAX,71H),只有涉及到的全部I/O许可位为0才能顺利访问。386支持I/O地址空间大小64K,所以I/O许可位有效部分最大为8KB。当前任务使用的I/O许可位图在TSS低端64K(用16位偏移,最大为64K)字节内,位串以字节为存储单位,所以要存储整个I/O许可位图时,偏移尽量保证低于64K – 8K = 56K。
c)I/O访问许可检查细节
步骤如下:
i)CPL <= IOPL是否成立,成立则直接跳转到第8步直接进行I/O访问
ii)取得位图开始偏移(TSS的I/O许可位图I偏移字段---TSS内偏移66H字节单元)
iii)计算字节偏移(I/O地址值右移3位---即除以8)
iv)计算位偏移以形成屏蔽码值(I/O地址右移出来的3位放字单元)
v)字节是否越界,是则引发通用保护异常(位图偏移+字节偏移+1<=段界限)
vi)从位图中读出两个字节(读写最快)
vii)进行位检查,不通过则引发通用保护异常
ix)进行I/O访问
在读取时,总是读取两个字节,由于I/O访问最多同时访问4个连续端口,最多也是分布在连续2个字节之内,所以在判断是否越界时要也仅要加1处理,为了在判断I/O许可位图最高字节上述流程同样适用,必须在I/O许可位图最后添加一个全1字节0ffh。I/O许可位图开始偏移加上8K所得值与TSS界限值二者较小者决定I/O许可位图有效末端偏移。当许可位图开始偏移大于56K时,将有部分位越过位图界限,从而无法因无法访问而触发通用保护异常(那部分位被认为是全1)。利用这个特点,可以大大减小I/O许可位图占用的存储单元,从而大大减小TSS。如:
1 ;演示任务任务状态段(TSS) 2 DemoTSSSeg segment para use16 3 DTSS TASKSS<> ;TSS低端部分 4 db 100h/8 dup(0ffh) ;对应I/O端口00H~0FFH 5 db 100h/8 dup(0) ;对应I/O端口100H~1FFH 6 db 0ffh ;IO许可位结束标志 7 DemoTSSLen = $ - DemoTSSSeg 8 DemoTSSSeg
上面的TSS只含有许可位图最初的200H位,对应端口0~1FFH,而其它位都被认为是1。之前实例中使用的格式,高度区只带IO许可位结束标志表示所有的位都为1。
2) 重要标志保护
386对EFLAGS标志中的IOPL、IF和VM这3个字段的处理比较特殊,只有较高权限等级的代码才能通过IRET、POPF、CLI和STI等指令来改变它们。下面是不同特权等级下对3个字段的处理情况:
特权等级 |
标志字段 |
||
VM |
IOPL |
IF |
|
CPL = 0 |
可变(除POPF指令外) |
可变 |
可变 |
0 < CPL <= IOPL |
不变 |
不变 |
可变 |
CPL > IOPL |
不变 |
不变 |
不变 |
可见,只有CPL = 0的情况下才能修改VM和IOPL,必须在IOPL同级或者更内层才能修改IF。须注意,在特权级不满足的条件下,其中IRET和POPF指令试图更改这3个字段并不引发异常,只是试图的修改操作并不会被成功执行。此外,POPF总是不能改变VM位,而PUSHF指令总是把0压入到VM位。
以上是关于《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护的主要内容,如果未能解决你的问题,请参考以下文章
《80X86汇编语言程序设计教程》十二 任务状态段控制门和控制转移
《80X86汇编语言程序设计教程》十 实模式与保护模式的切换实例