天天看點

Protection 6 ---- Page-Level Protection頁級别的保護類型#PF異常例子參考

頁級别的保護類型

對于分頁結構的保護分為兩種,一種是對于通路權限的檢查,一種是對于線性位址是否存在轉換進行檢查。

通路權限

分頁結構中的一些位是用來規定頁的通路權限的,例如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的類型:

Protection 6 ---- Page-Level Protection頁級别的保護類型#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
           

最後的執行結構:

Protection 6 ---- Page-Level Protection頁級别的保護類型#PF異常例子參考

從執行結果可以看出,從第一次列印的結構來看,對線性位址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體系探索及程式設計》