修改段寄存器时的保护
在保护模式下,修改段寄存器需要带入段选择子,此时会校验带入值的合法性,然后从全局描述符表GDT中找到相应的描述符并加载到描述符高速缓存器中,此时会校验描述符的完整性和正确性。
在全局描述符表GDTR中,有GDT线性基地址和界限值两部分,选择子所选择的描述符必须在边界值以内,也就是索引号 * 8 + 7 <= 边界
,如果选择子对应的描述符超出了界限值,处理器会响应一个异常中断13,并且不会改变段寄存器的内容。
如果描述符S=1,便会校验type域,type域必须是有效的,比如0000就是一个无效type。然后会检查type域是否符合将带入的段寄存器的用途,段寄存器与type域的匹配关系如下表:
从上表可知,数据段是无论如何都不能带入cs的;对于ds, es, fs, gs,数据段无论可读可写都是可以带入的,如果代码段想带入这四个寄存器,只有一种情况就是R=1;不过对于段寄存器ss,只有数据段可读可写才能带入,其它任何情况都无法带入。
检查完type域,再需要对P位进行检查,如果P=1则可以正常加载描述符,同时将A位置1;如果P=0,说明此时描述符所描述的段并不在内存中,此时处理器会引发一个11号中断,通常需要定义一个中断处理程序来将段从硬盘加载到内存中,然后将P位置1,与其它中断不同,这类中断不会返回到下一条指令执行,而是返回到引发中断的那条指令,这样处理器就可以重新加载段描述符了。
另外,对于ds, es, fs和gs,可以向其传送索引为0的选择子,比如
mov eax, 0
mov ds, eax
尽管这两条指令在执行时不会有问题,但是在内存访问时比如mov [ebx], ax
,会导致异常中断,处理器用它来保证系统安全。
不过对于cs和ss,不允许向其传送索引为0的选择子。
代码段执行时的保护
每个段都有自己的界限,代码段也不例外,在代码段取指令执行时,要求EIP+指令长度-1 <= 段界限
。
向上扩展的段用作栈段时的保护
首先澄清一个问题:段的扩展方向和推进方向并不是一回事,虽说栈是向下推进的,但是它所对应的描述符E位可以是0,也就是说可以向上扩展,段的扩展方向仅用于指示处理器如何检查栈的操作。
对于一个向上扩展的数据段,在实际写入数据前,需要按这个规则进行检查:操作数的有效地址 + 操作数的大小 - 1 <= 实际使用的段界限
。
e.g.定义一个数据段描述符:
0x004092006c0007ff
可知,段的线性基地址为0x00006c00,段界限为0x007ff,G位是0,B位是1说明使用ESP,E=0说明向上扩展。
如果用它来作为栈段,首先需要将ESP设置为段的大小,即段界限+1=0x800。
假如此时要压入一个双字,也就是说需要从偏移量为0x800-4=0x7FC处写入4个字节,那么操作数的有效地址就是0x7FC,代入上面的公式:0x7FC+4-1=0x7FF,刚好等于段界限,指令可以正常执行。
如果说一开始ESP的值设为0x801,那么向其中压入数据必然会超出段界限,此时便会报错。
向下扩展的段用作栈段时的保护
e.g.定义一个数据段描述符:
0x00cf96007c00fffe
根据描述符提供的信息,段基地址为0x00007c00,段界限为0xffffe,G=1说明粒度是4K字节,则实际使用的段界限为0xffffe * 0x1000 + 0xfff = 0xFFFFEFFF
,B位=1,说明该段默认使用ESP,理论最大可用偏移量为0xFFFFFFFF,对于向下扩展的段来说,活动范围就是0xFFFFFFFF~0xFFFFEFFF+1(0xFFFFF000),X=0说明是数据段,E=1说明向下扩展,W=1说明该段可读可写。
对于向下扩展的段,处理器要求操作数的有效地址 > 实际使用的段界限
。在向栈段压入数据前,需要初始化ESP的值为0xFFFFFFFF+1=0,即mov esp, 0
,如果此时向其中压入一个双字,那么操作数的有效地址就是0-4=0xFFFFFFFC,0xFFFFFFFC > 0xFFFFEFFF,未超出段界限,然后再用段基地址0x00007c00+0xFFFFFFFC=0x00007BFC,也就是说会向线性地址为0x7BFC处写入四个字节。