Protection 2 ---- Limit Checking

来源:互联网 发布:ftp上传工具 for mac 编辑:程序博客网 时间:2024/06/09 15:31

这篇文章中主要描述了CPU在运行过程中对于表限和段限的检查,这里描述检查机制都是在相同权限级别条件下进行的,这里的示例代码都运行在ring 0下。文章中并没有给出全部的代码,只是给出了关键的部分,这些代码都是根据上篇文章《Protection 1》中的代码修改的,同样这篇文章中涉及到的代码也都托管在github上,可以到https://github.com/activesys/learning_cpu/tree/master/x86/protection_2上去下载。


Checking for GDT

CPU在运行的过程中都会通过GDT来获得代码段或者数据段,那么运行的第一项检查就是关于GDT的检查,这些检查分为表限检查和NULL Descriptor检查。

GDT Limit Checking

GDTR中包含一个16位的表限,CPU使用这个表限防止程序访问GDT的时候越界,当发生越界的时候会产生#GP异常。

越界的第一种形式就是索引直接越界,在上一篇文章中提供的基础代码的基础上提供了一个invalid_selector,它的索引位置已经大大超出了GDT的表限:

.equ invalid_selector, 0x0100
但加载invalid_selector时就会导致#GP异常:

    # trigger #GP exception    movw $invalid_selector, %ax    movw %ax, %ds

越界的第二种形式是索引的位置在GDT表限内,但是被索引的描述符整体越界了,为了测试这种情况,我们将GDT的表限缩减一个字节:

# GDTgdt_start:null_descriptor:    .quad 0x0000000000000000code_descriptor:    .quad 0x00cf9a000000ffffvedio_descriptor:    .quad 0x00cf920b8000ffffstack_descriptor:    .quad 0x00cf92007000ffffint_descriptor:    .quad 0x00cf9a001000ffffdata_descriptor:    .quad 0x00cf92000000ffffgdt_end:.equ gdt_len, gdt_end - gdt_start - 2gdt_pointer:    .short gdt_len    .int   gdt_start

将GDT的表限缩减了一个字节,这样data_descriptor就不完整了,当向%ds中加载data_selector的时候访问data_descriptor就会因为越界1个字节导致#GP异常:

    # trigger #GP exception    movw $data_selector, %ax    movw %ax, %ds
这两个例子的运行结果都是一样的:

GDT NULL Descriptor

GDT中的第一个描述符叫做“null descriptor”,CPU没有使用这个描述符,当把它加载到%cs或者%ss的时候会产生#GP,加载到%ds, %es, %fs和%gs的时候不会产生#GP,不过通过加载了null descriptor的段寄存器来访问内存就会产生#GP。其实null descriptor的检查算不上是表限检查,应该算作类型检查。

我们首先来看看将null descriptor加载到%ss的情况,在代码的开头定义了null_selector:

.equ null_selector, 0x00
在保护模式代码中加载了null descriptor:

    # trigger #GP exception    movw $null_selector, %ax    movw %ax, %ss
我们再来看看将null descriptor加载到%ds, %es, %fs或者%gs,并且通过这些段寄存器访问内存的情况,在保护模式代码中将null descriptor加载到%fs和%gs:

    movw $null_selector, %ax    movw %ax, %fs    movw %ax, %gs
然后通过%fs来访问内存:
    # trigger #GP exception    movl %fs:(%ebx), %ecx

这两段代码的运行结果都是一样的:



Checking for Segment

通过了GDT的检查之后就可以获得GDT中相应的段描述符了,然后通过描述符获得实际想要访问的代码和数据,但是在访问之前,CPU还有针对段进行检查。这里只介绍段限的检查。段描述符中的段限是20位的,最高可设置为0xfffff,但这并不意味着段限就是0~0xfffff,因为段描述符中还有一些其他的位影响段限检查,典型的有G(Granularity)标志位以及Expand-Down类型的数据段。

G flag

G标志位表示是段描述符的第23位,它表示段限粒度,当G标志位被清除的时候,表示段限粒度的单位是字节,当G被设置的时候表示段限粒度单位是4K字节。这个小节中不考虑Expand-Down类型的数据段,我们来看看两种粒度单位下是如何实施段限检查的:

为了测试,我们给%fs和%gs分别定义了两个数据段,加载给%fs的数据段的G标志位被清空,加载给%gs的数据段的G标志位被设置,这两段代码的段限都被设置成了0x0f:

fs_descriptor:    .quad 0x004092000000000fgs_descriptor:    .quad 0x00c092000000000f
先来看看G标志位被清空的情况下的段限检查,上面的代码将段限设置成了0x0f,所以加载给%fs的段的有效偏移就是0x00~0x0f,我们采用一个超过段限的偏移来访问内存:
    # trigger #GP exception    movw $fs_selector, %ax    movw %ax, %fs    movl $0x10, %ebx    movl %fs:(%ebx), %ecx #GP
当访问内存的时候就会发生#GP。

再来看看G标志位被设置的情况,加载给%gs的段的段限也被设置成了0x0f,但是由于段限粒度是4K字节,那么有效偏移就是0x00~0xffff,同样我们采用一个超过段限的偏移来访问内存:

    # trigger #GP exception    movw $gs_selector, %ax    movw %ax, %gs    movl $0x10000, %ebx    movl %gs:(%ebx), %ecx #GP
同样当访问内存的时候发生了#GP。

这两段代码的运行结果一样:


Expand-Down

数据段的段限检查不仅仅受到G标志位的影响,还受到Type.E以及B标志位的影响,当E被设置的时候,数据段是Expand-Down类型的数据段,这个时候的段限表示的是段内不允许访问的最后地址,它的段限的下限是(offset-limit+1),上限要看B标志位,当B标志位被清空的时候,上限是0xffff(64K),当B标志位被设置的时候,上限是0xffffffff(4G)。

为了测试Expand-Down类型的段限检查,代码中设置了两个段:

fs_descriptor:    .quad 0x00409200000000ffgs_descriptor:    .quad 0x00409600000000ff
这两个段分别加载给%fs和%gs,加载给%fs的段是普通的数据段,加载给%gs的段是expand-down类型的数据段,两个段的段限都是0xff。

普通的段的段限检查和前面的示例代码的段限检查的机制是一致的,我们使用一个超过段限的偏移量来访问内存就会触发#GP:

    # trigger #GP exception    movw $fs_selector, %ax    movw %ax, %fs    movl $0x100, %ebx    movl %fs:(%ebx), %ecx #GP
但是expand-down类型的段就不同了,如果我们使用0x100作为偏移量是不会触发#GP的,我们使用0xff作为偏移量来触发#GP:
    # trigger #GP exception    movw $gs_selector, %ax    movw %ax, %gs    movl $0xff, %ebx    movl %gs:(%ebx), %ecx #GP
两段代码的运行结果相同:

Limit Checking for Stack Segment

对于堆栈段的段限检查和其他的段是一样的,但是产生的异常不同,如果堆栈段越界会产生#SS(Stack-Segment Falut)异常而不是#GP异常。


Checking for IDT

IDT的表限检查和GDT是一样的,不同的是IDT不存在null descriptor的问题,我们给出一个简单的例子来展示IDT的表限检查。

首先将IDT的表限缩短一个门描述符的长度(8个字节):

idt_pointer:    .short 0x7f8    .int   0x00
然后int指令产生中断:

    int $0xff
0xff中断的位置超出了IDT的表限,产生了#GP异常,运行结果如下


问题

上述这些例子中qemu只支持GDT和IDT的段限检查,其他的例子在qemu中均不能够得到正确的结构,不过在vmware和bochs中所有的例子运行都是正确的。


参考

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》
《自己动手写操作系统》
《x86/x64体系探索与编程》


原创粉丝点击