Protection 6 ---- Page-Level Protection

页级别的保护类型

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

访问权限

分页结构中的一些位是用来规定页的访问权限的,例如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_handler
pf_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 error
get_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_done

get_pdpte_low:
    popl %edi
    movl (%ebx, %eax, 8), %edx
    btsl $0, %edx
    movl %edx, (%ebx, %eax, 8)
    jnc do_PF_handler_done

get_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_done

get_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_done

get_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_done

get_pte_low:
    popl %edi
    movl (%ebx, %eax, 8), %edx
    btsl $0, %edx
    movl %edx, (%ebx, %eax, 8)
    jc do_PF_handler_done

do_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体系探索及编程》





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值