Protection 6 ---- Page-Level Protection

来源:互联网 发布:淘宝苏宁易购退笔记本 编辑:程序博客网 时间:2024/05/20 05:05

页级别的保护类型

对于分页结构的保护分为两种,一种是对于访问权限的检查,一种是对于线性地址是否存在转换进行检查。

访问权限

分页结构中的一些位是用来规定页的访问权限的,例如R/W,U/S以及XD。这些位在不同的访问模式下起着不同的限制作用。对于一个线性地址的访问有两种模式:supervisor-mode以及user-mode。当CPL<3的时候是supervisor-mode,CPL==3是user-mode。下面这些规则是Intel手册中列举出来的不同的访问权限:

  • supervisor-mode
    • 读数据
      • 可以从任何线性地址读取数据
    • 写数据
      • CR0.WP==0,可以向任何线性地址写数据
      • CR0.WP==1,只能向地址转换过程中使用的分页结构中R/W==1的线性地址写数据
    • 执行
      • 32-Bit Paging或者IA32_EFER.NXE==0,访问权限依赖于CR4.SEMP
        • CR4.SEMP==0,可以执行任何线性地址中的指令
        • CR4.SEMP==1,只能执行转换过程中使用的分页结构的U/S==0的线性地址中的指令
      • PAE Paging或者IA-32e Paging并且IA32_EFER.NXE==1,访问权限也依赖于CR4.SEMP
        • CR4.SEMP==0,只能执行转换过程中使用的分页结构的XD==0的线性地址中的指令
        • CR4.SEMP==0,只能执行转换过程中使用的分页结构的U/S==0并且XD==0的现行地址中的指令
  • user-mode
    • 读数据
      • 只能读取转换过程中使用的分页结构的U/S==1的线性地址中的数据
    • 写数据
      • 只能向转换过程中使用的分页结构的U/S==1并且R/W==1的线性地址中写数据
    • 执行
      • 32-Bit Paging或者IA32_EFER.NXE==0,可以执行地址转换过程中使用的分页结构的U/S==1的线性地址中的指令
      • PAE Paging或者IA-32e Paging并且IA32_EFER.NXE==1,可以执行地址转换过程中分页结构的U/S==1并且XD==0的线性地址中的指令

没有地址转换

另一种检查是检查线性地址在现有的分页结构中有没有转换,所谓没有转换就是线性地址在转换过程中使用的分页结构的P==0或者是保留位被设置。


#PF异常

如果对于一个线性地址的转换过程中违反了上述两种检查,那么就会触发#PF异常。下图是#PF的error code,它描述了#PF的类型:


  • P:
    • 0:异常是由于不存在的页引起的
    • 1:异常是由于违反了Page-Level保护引起的
  • W/R:
    • 0:异常是由于读访问引起的
    • 1:异常是由于写访问一起的
  • U/S:
    • 0:异常是由于supervisor-mode引起的
    • 1:异常是由于user-mode引起的
  • RSVD:
    • 0:异常不是由于保留位被设置引起的
    • 1:异常是由于某些分页结构的保留位被设置引起的
  • I/D:
    • 0:异常不是由于取指引起的
    • 1:异常是由于取指引起的


例子

这里的例子主要是模拟由于没有地址转换导致#PF异常,并且在#PF异常处理程序中修改错误的情况。

首先要准备一段可执行的代码,这段代码要拷贝到能够引起#PF异常的线性地址中以便测试使用。

code_start:    movl $puts, %ebx    movl $dump_pae_page, %edx    movl $msg3, %esi    call *%ebx    movl $println, %eax    call *%eax    movl $0x400000, %esi    call *%edx    movl $println, %eax    call *%eax    jmp .code_end:
这段代码也是通过dump_pae_page来打印一个线性地址的转换过程。

接下来要将这段代码拷贝到测试的地址中,我们使用了0x400000地址为测试地址,不过拷贝的过程要在开启分页之前进行,否则拷贝的过程就会导致#PF。

    /*     * copy code to 0x400000     */    movl $code_start, %esi    movl $0x400000, %edi    movl $code_end, %ecx    subl %esi, %ecx    rep movsb
测试代码准备好了,调用init_pae32_paging来初始化PAE Paging结构,这个结构和《PAE Paging》中的分页结构相同,不过就是在0x400000地址转换过程中PDE的保留位被设置了,PTE的P标志被清0,这两种情况都会导致#PF。
################################################################ init_pae32_paging:init_pae32_paging:    movl $PDPT_BASE, %esi    call clear_4k_page    movl $0x201000, %esi    call clear_4k_page    movl $0x202000, %esi    call clear_4k_page    # PDPTE[0]    movl $PDPT_BASE, %esi    movl $0x201001, (%esi)    movl $0x00, 4(%esi)    # PDE[0] 0x00 ~ 0x1fffff (2M)    # PDE[1] 0x200000 ~ 0x3fffff (2M)    # PDE[2] 0x400000 ~ 0x400fff (4K)    movl $0x201000, %esi    movl $0x00, %eax    movl $0x00000087, (%esi, %eax, 8)    movl $0x00, 4(%esi, %eax, 8)    inc %eax    movl $0x00200087, (%esi, %eax, 8)    movl $0x00, 4(%esi, %eax, 8)    inc %eax    movl $0x00202007, (%esi, %eax, 8)    movl $0x70000000, 4(%esi, %eax, 8)    # PTE[0] 0x400000 ~ 0x400fff (4K)    movl $0x202000, %esi    movl $0x00400000, (%esi)    /*    movl xd_bit, %eax    movl %eax, 4(%esi)    */    ret
然后就是编写#PF处理代码了,这里能够修改线性地址转换过程中由于数据结构中保留位被设置以及P标志为0的异常。
################################################################ PF_handler():PF_handler:    jmp do_PF_handlerpf_msg1:  .asciz  "----> Now, enter #PF handler, occur at: 0x"pf_msg2:  .asciz  "----> Error Code: 0x"pf_msg3:  .asciz  "----> fixup <----"do_PF_handler:    popl %esi    pushl %ecx    pushl %edx    pushl %ebx    movl %esi, %ebx    # puts error address    movl $pf_msg1, %esi    call puts    movl %cr2, %ecx    movl %ecx, %esi    call print_int_value    call println    # puts error code    movl $pf_msg2, %esi    call puts    movl %ebx, %esi    call print_int_value    call println    call get_maxphyadd    pushl %ecx    leal -64(%eax), %ecx    negl %ecx    movl $-1, %edi    shll %cl, %edi    shrl %cl, %edi    popl %ecx# fix errorget_pdpte:    movl %ecx, %eax    shrl $30, %eax    andl $0x03, %eax    movl $PDPT_BASE, %ebx    # PDPTE    pushl %edi    movl 4(%ebx, %eax, 8), %edx    notl %edi    andl %edx, %edi    jz get_pdpte_low    popl %edi    andl %edi, %edx    movl %edx, 4(%ebx, %eax, 8)    jmp do_PF_handler_doneget_pdpte_low:    popl %edi    movl (%ebx, %eax, 8), %edx    btsl $0, %edx    movl %edx, (%ebx, %eax, 8)    jnc do_PF_handler_doneget_pde:    #PDE    movl %edx, %ebx    andl $0xfffff000, %ebx    movl %ecx, %eax    shrl $21, %eax    andl $0x01ff, %eax    pushl %edi    movl 4(%ebx, %eax, 8), %edx    notl %edi    andl $0x7fffffff, %edi    andl %edx, %edi    jz get_pde_low    popl %edi    orl $0x80000000, %edi    andl %edi, %edx    movl %edx, 4(%ebx, %eax, 8)    jmp do_PF_handler_doneget_pde_low:    popl %edi    movl (%ebx, %eax, 8), %edx    btsl $0, %edx    movl %edx, (%ebx, %eax, 8)    jnc do_PF_handler_done    bt $7, %edx    movl %edx, (%ebx, %eax, 8)    jc do_PF_handler_doneget_pte:    #PTE    movl %edx, %ebx    andl $0xfffff000, %ebx    movl %ecx, %eax    shrl $12, %eax    andl $0x01ff, %eax    pushl %edi    movl 4(%ebx, %eax, 8), %edx    notl %edi    andl $0x7fffffff, %edi    andl %edx, %edi    jz get_pte_low    popl %edi    orl $0x80000000, %edi    andl %edi, %edx    movl %edx, 4(%ebx, %eax, 8)    jmp do_PF_handler_doneget_pte_low:    popl %edi    movl (%ebx, %eax, 8), %edx    btsl $0, %edx    movl %edx, (%ebx, %eax, 8)    jc do_PF_handler_donedo_PF_handler_done:    movl $pf_msg3, %esi    call puts    call println    call println    popl %ebx    popl %edx    popl %ecx    iret

最后在开启分页之后,首先对线性地址0x400000的转换过程进行打印,然后逃转到0x400000地址去执行其中的指令,由于0x400000地址中的指令也是打印对于线性地址0x400000的转换过程,所以执行结构应该看到两次对于0x400000线性地址的转换过程的打印结构。

    movl $msg3, %esi    call puts    call println    movl $0x400000, %esi    call dump_pae_page    call println    ljmp $KERNEL_CODE32_SELECTOR, $0x400000

最后的执行结构:


从执行结果可以看出,从第一次打印的结构来看,对线性地址0x400000的转换中使用的PDE的保留位被设置了,PTE的P标志被清0,所以没有对线性地址0x400000的转换。接下来的两次#PF正是由于跳转到0x400000地址去执行指令导致的。

第一次#PF是由PDE的保留位被设置导致的,从错误代码中可以看到,0x19表示P==1,RSVD==1,I/D==1,这说明异常是由于违反了页级别的保护引起的,具体是由于一些分页结构的保留位被设置引起的,同时是在取指令时引起的。接下来#PF处理过程修复了这个错误。

第一次#PF异常处理结束后,会重现执行跳转代码,但是这是线性地址0x400000仍然没有转换,所以导致了第二次#PF异常。

第二次#PF是由PTE的P标记被清0导致的,从错误代码中可以看出,0x10表示P==0,I/D==1,这说明异常是由于页不存在导致的,同时是在取指令时引起的。接下来#PF处理过程也修改了这个错误。

两种错误都被修正之后,代码可以正确的跳转到0x400000地址去执行了,执行的结构就是打印出线性地址0x400000被转换的过程,从结果上看之前的错误都被#PF处理过程修复了。


参考

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》

《x86/x64体系探索及编程》





原创粉丝点击