处理器在变更段寄存器以及隐藏的描述符高速缓存器的内容时,要检查其代入值的合法性。
第一步检查:
有些指令显式修改段寄存器,有些指令隐式修改段寄存器。修改段寄存器时,处理器把指令中给出的选择子传送到段寄存器的选择器部分。但在完成传送之前,要确认选择子是正确的,并且该选择子选择的描述符也是正确的。
描述符在内存中的地址,必须在描述符表的地址范围内。换句话说,索引号乘以8得到的数值,必须位于描述符表的边界范围之内。也就是说,处理器从GDT中取某个描述符时,就要求描述符的8个字节都在GDT边界之内,也就是索引号×8+7小于等于边界。
如果检查到指定的段描述符,其位置超过表的边界时,处理器中止处理,产生异常中断13,同时段寄存器中的原值不变。
第二步检查:
处理器从GDT中取得描述符后,紧接着还要对描述符的类别进行确认。比如,若描述符的类别是只执行的代码段,则不允许加载到除CS之外的其他段寄存器中。
(1)描述符的类别字段必须是有效的值,像0000就是无效的。
(2)检查描述符的类别是否和段寄存器的用途匹配。
(3)检查描述符中的P位。如果P=0,表明虽然描述父母已被定义,但该段实际上并不存在于物理内存中。此时,处理器中止处理,引发异常中断11。一般来说,应当定义一个中断处理程序,把该描述符所对应的段从硬盘等外部存储设备调入内存,然后置P位。中断返回时,处理器将再次尝试刚才的操作。如果P=1,则处理器将描述符加载到段寄存器的描述符高速缓存器,同时置A位。
上面的检查如果全部通过,处理器就将选择子加载到段寄存器的选择器。对于CS和SS的选择器来说,不允许向其传送为0的选择子。
检测点12.1
1、若某段描述符中的段界限是0xFFFFC,当粒度为字节和4KB时,实际使用的段界限是多少?
答:当粒度为字节时,实际使用的段界限为:0x000FFFFC。当粒度为4KB时,实际使用的段界限为:0xFFFFC×0x1000+0xFFF=0xFFFFCFFFF。2、若GDT的界限为0x87,寄存器AX的内容为0x0088,则执行指令mov ds,ax时,处理器会产生异常吗?
答:处理器从GDT中取某个描述符时,要求描述符的8个字节都在GDT边界之内,也就是索引号×8+7小于等于边界。本题中段选择子为0x0088,索引号为10001B,即17。17×8+7=143,而143D>0x87,所以处理器中止处理,产生异常中断13。
12.4 地址变换时的保护
【12.4.1 代码段执行时的保护】
在32位模式下,尽管段的信息在描述符表中,但是,一旦相应的描述符被加载到段寄存器的描述符高速缓存器,则处理器取指令和执行指令时,将不再访问描述符表,而是直接使用段寄存器的描述符高速缓存器,从中取得线性基地址,同指令指针寄存器EIP的内容相加,共同形成32位的物理地址从内存中取得下一条指令。不过,在指令实际开始执行之前,处理器必须检验其存放地址的有效性,以防止执行超出允许范围之外的指令。
要执行的那条指令,其长度减1后,与EIP寄存器的值相加,结果必须小于等于实际使用的段界限,否则引发处理器异常。即:0≤(EIP+指令长度-1)≤实际使用的段界限。
比如,当前代码段粒度为字节,段界限值0x001FF,则EIP寄存器的数值加上最后一条指令的长度减1,得到的结果必须小于等于0x000001FF,如果超出这个数值,必然引发异常中断。又比如,假设当前代码段的粒度是4KB,段界限值是0x001FF,故实际使用的段界限值是0x1FF×0x1000+0xFFF=0x001FFFFF。则任何时候,EIP寄存器的内容加上取得的指令长度减1,都必须小于等于0x001FFFFF,否则将引发处理器异常中断。
任何指令都不允许,也不可能向代码段写入数据。而且,只有在代码段可读的情况下(由其描述符指定),才能由指令读取器内容。
【12.4.2 栈操作时的保护】
栈本身始终总是向下增长的,即,向低地址方向推进。段的扩展方向用于处理器的界限检查,而对栈的性质以及在栈上进行的操作没有关系。(本章只讨论32位的栈)
在栈段中,实际使用的段界限也和粒度位有关。如果G=0,实际使用的段界限就是描述符中记载的段界限;如果G=1,则实际使用的段界限为:描述符中的段界限值×0x1000+0xFFF。
栈段是向下扩展的,每当往栈中压入数据时,ESP的内容要减去操作数的长度。所以,和向高地址方向扩展的段相比,非常重要的一点就是,实际使用的段界限就是段内不允许访问的最低端偏移地址。至于最高端的地址,则没有限制,最大可以是0xFFFFFFFF。也就是说,在进行栈操作时,必须符合以下规则:实际使用的段界限+1≤(ESP的内容-操作数的长度)≤0xFFFFFFFF。
例如:当G=0时,假如描述符中的段界限是0x07A00,此时实际使用的段界限也是0x07A00。假设现在ESP的内容是0x00007A04,执行“push edx”会怎样呢?因为要压入一个双字,所以ESP-4=0x7A00,因为它小于段界限值+1(即0x07A01),所以不允许执行该操作。但如果执行“push ax”,因为要压入的是一个字,所以ESP-2=0x7A02,该值大于段界限值+1,所以允许执行。
本程序中,栈段的线性基地址0x00007C00,段界限为0xFFFFE,粒度为4KB,栈指针寄存器ESP为0。因为粒度为4KB,所以实际使用的段界限值为:0xFFFFE×0x1000+0xFFF=0xFFFFEFFF。所以栈的操作必须符合:0xFFFFEFFF+1≤(ESP的内容-操作数的长度)≤0xFFFFFFFF
栈内元素的线性地址是栈线性基地址+ESP的值,所以这个栈最低端的有效物理地址是0x00007C00+0xFFFFF000=0x00006C00,最高端的有效物理地址是0x00007C00+0xFFFFFFFF=0x00007BFF。也就是说,当前程序定义的栈空间介于地址0x00006C00~0x00007BFF之间,大小是4KB。
【12.4.2 栈操作时的保护】
栈本身始终总是向下增长的,即,向低地址方向推进。段的扩展方向用于处理器的界限检查,而对栈的性质以及在栈上进行的操作没有关系。(本章只讨论32位的栈)
在栈段中,实际使用的段界限也和粒度位有关。如果G=0,实际使用的段界限就是描述符中记载的段界限;如果G=1,则实际使用的段界限为:描述符中的段界限值×0x1000+0xFFF。
栈段是向下扩展的,每当往栈中压入数据时,ESP的内容要减去操作数的长度。所以,和向高地址方向扩展的段相比,非常重要的一点就是,实际使用的段界限就是段内不允许访问的最低端偏移地址。至于最高端的地址,则没有限制,最大可以是0xFFFFFFFF。也就是说,在进行栈操作时,必须符合以下规则:
实际使用的段界限+1≤(ESP的内容-操作数的长度)≤0xFFFFFFFF
例如:当G=0时,假如描述符中的段界限是0x07A00,此时实际使用的段界限也是0x07A00。假设现在ESP的内容是0x00007A04,执行“push edx”会怎样呢?因为要压入一个双字,所以ESP-4=0x7A00,因为它小于段界限值+1(即0x07A01),所以不允许执行该操作。但如果执行“push ax”,因为要压入的是一个字,所以ESP-2=0x7A02,该值大于段界限值+1,所以允许执行。
本程序中,栈段的线性基地址0x00007C00,段界限为0xFFFFE,粒度为4KB,栈指针寄存器ESP为0。因为粒度为4KB,所以实际使用的段界限值为:0xFFFFE×0x1000+0xFFF=0xFFFFEFFF。所以栈的操作必须符合:
0xFFFFEFFF+1≤(ESP的内容-操作数的长度)≤0xFFFFFFFF
栈内元素的线性地址是栈线性基地址+ESP的值,所以这个栈最低端的有效物理地址是0x00007C00+0xFFFFF000=0x00006C00,最高端的有效物理地址是0x00007C00+0xFFFFFFFF=0x00007BFF。也就是说,当前程序定义的栈空间介于地址0x00006C00~0x00007BFF之间,大小是4KB。
这里所说的数据段,特指向上扩展的数据段,有别于栈和向下扩展的数据段。
因为是向上扩展的,所以代码段的检查规则同样适用于数据段。不同之处仅仅在于,对于取指令来说,是否越界取决于指令的长度;而对于数据段来说,则取决于操作数的尺寸。
在任何时候,段界限之外的访问企图都会被阻止,并引发异常中断。在32位处理器上,尽管段界限的检查总在进行着,但如果段界限具有最大值,则对任何内存地址的访问都将不会违例。任何一个内存单元都将被允许访问,针对段界限的检查都会获得通过。
当前栈段描述符的B位是1,基地址为0x00700000,界限值为0xFFFFE,那么,在32位模式下,该栈段的有效地址范围是0x00700000~( )。当ESP的内容为0xFFFFF002时,还能压入一个双字吗?为什么?
答:该栈段的有效地址范围是0x00700000~0x006FFFFF。
因为段界限值为0xFFFFE,所以实际使用的段界限值为0xFFFFE×0x1000+0xFFF=0xFFFFEFFF。因为ESP的最大值是0xFFFFFFFF,所以处理器的检查规则是:0xFFFFF000≤(ESP的内容-操作数的长度)≤0xFFFFFFFF。所以最低有效地址:0x00700000+0xFFFFF000=0x006FF000;最高有效地址:x00700000+0xFFFFFFFF=0x006FFFFF。
ESP-4=0xFFFFF002-4=0xFFFFEFFE。符合检查规则,因此可以压入一个双字。
12.5 使用别名访问代码段对字符排序
程序第59行、第60行,段选择器DS中传入索引号3的段描述符。该段描述符为代码段的别名。使用该段描述符,可以对代码段的内容进行读写操作。比如例子中冒泡排序就是读写操作。
XCHG指令,交换指令,交换源操作数与目的操作数的内容。