又是一篇內核函數分析的博文,我個人覺得Windows的內核是最好的老師,當你想實現一個功能之前可以看看Windows內核是怎么做的,說不定就有靈感呢:)
首先看下官方的注釋說明:
/*++ Routine Description: For a given virtual address this function returns TRUE if no page fault will occur for a read operation on the address, FALSE otherwise. Note that after this routine was called, if appropriate locks are not held, a non-faulting address could fault. Arguments: VirtualAddress - Supplies the virtual address to check. Return Value: TRUE if no page fault would be generated reading the virtual address, FALSE otherwise. Environment: Kernel mode. --*/
WDK文檔中給出的功能描述是這樣的:The MmIsAddressValid routine checks whether a page fault will occur for a read or write operation at a given virtual address.根據描述來看這個函數的功能只是去檢查讀寫操作會不會觸發一個頁錯誤,但是作為一個常用函數,我們常常用這個函數來檢查地址合不合法,這次就在源碼里看下具體的流程,主要目的是搞清楚這個函數是怎么判斷一個函數會不會觸發頁錯誤的。
1 BOOLEAN 2 MiIsAddressValid ( 3 IN PVOID VirtualAddress, 4 IN LOGICAL UseForceIfPossible 5 ) 6 { 7 PMMPTE PointerPte; 8 9 10 // 11 // If the address is not canonical then return FALSE as the caller (which 12 // may be the kernel debugger) is not expecting to get an unimplemented 13 // address bit fault. 14 // 15 16 if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) { 17 return FALSE; 18 } 19 20 21 22 23 PointerPte = MiGetPdeAddress (VirtualAddress); 24 if (PointerPte->u.Hard.Valid == 0) { 25 return FALSE; 26 } 27 28 if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) { 29 return TRUE; 30 } 31 32 PointerPte = MiGetPteAddress (VirtualAddress); 33 if (PointerPte->u.Hard.Valid == 0) { 34 return FALSE; 35 } 36 37 // 38 // Make sure we're not treating a page directory as a page table here for 39 // the case where the page directory is mapping a large page. This is 40 // because the large page bit is valid in PDE formats, but reserved in 41 // PTE formats and will cause a trap. A virtual address like c0200000 (on 42 // x86) triggers this case. 43 // 44 45 if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) { 46 return FALSE; 47 } 48 49 return TRUE; 50 }
代碼出人意外的簡單,很明顯,這是利用了分頁機制去查詢。先查下頁目錄項是否為空,然后再看一下頁表項是否為空。至於28、29行應該是判斷是不是直接使用PDE作為一級表吧,但是現在應該沒有這么用的吧。
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) { return TRUE; }
如上就是一個判斷。
而MiGetPdeAddress和MiGetPteAddress 其實是兩個宏,這個宏我們也可以拿來用。
#define MiGetPdeAddress(va) \ ((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PDI_SHIFT) << PTE_SHIFT) + PDE_BASE))
#define MiGetPteAddress(va) \ ((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE))
#define VIRTUAL_ADDRESS_BITS 48 #define VIRTUAL_ADDRESS_MASK ((((ULONG_PTR)1) << VIRTUAL_ADDRESS_BITS) - 1)
注意,每個進程都有自己的進程頁表和頁目錄但是內核在分配一個進程的地址空間時會把PD給復制一份,以便於訪問。
由以上的分析可以看出並沒有所謂的驗證地址是否可讀寫的功能,我們有時候會把它和ProbeForRead(),ProbeForWrite()這兩個函數相混淆。這兩個函數才是來驗證地址是否可讀寫的函數,但是僅限於用戶地址空間的地址。從WDK中可以看到如下的描述:The ProbeForWrite routine checks that a user-mode buffer actually resides in the user-mode portion of the address space, is writable, and is correctly aligned.我們來看下這個函數的實現。
1 VOID 2 ProbeForWrite ( 3 __inout_bcount(Length) PVOID Address, 4 __in SIZE_T Length, 5 __in ULONG Alignment 6 ) 7 8 /*++ 9 10 Routine Description: 11 12 This function probes a structure for write accessibility and ensures 13 correct alignment of the structure. If the structure is not accessible 14 or has incorrect alignment, then an exception is raised. 15 16 Arguments: 17 18 Address - Supplies a pointer to the structure to be probed. 19 20 Length - Supplies the length of the structure. 21 22 Alignment - Supplies the required alignment of the structure expressed 23 as the number of bytes in the primitive datatype (e.g., 1 for char, 24 2 for short, 4 for long, and 8 for quad). 25 26 Return Value: 27 28 None. 29 30 --*/ 31 32 { 33 34 ULONG_PTR EndAddress; 35 ULONG_PTR StartAddress; 36 37 #define PageSize PAGE_SIZE 38 39 // 40 // If the structure has zero length, then do not probe the structure for 41 // write accessibility or alignment. 42 // 43 44 if (Length != 0) { 45 46 // 47 // If the structure is not properly aligned, then raise a data 48 // misalignment exception. 49 // 50 51 ASSERT((Alignment == 1) || (Alignment == 2) || 52 (Alignment == 4) || (Alignment == 8) || 53 (Alignment == 16)); 54 55 StartAddress = (ULONG_PTR)Address; 56 if ((StartAddress & (Alignment - 1)) == 0) { 57 58 // 59 // Compute the ending address of the structure and probe for 60 // write accessibility. 61 // 62 63 EndAddress = StartAddress + Length - 1; 64 if ((StartAddress <= EndAddress) && 65 (EndAddress < MM_USER_PROBE_ADDRESS)) { 66 67 // 68 // N.B. Only the contents of the buffer may be probed. 69 // Therefore the starting byte is probed for the 70 // first page, and then the first byte in the page 71 // for each succeeding page. 72 // 73 // If this is a Wow64 process, then the native page is 4K, which 74 // could be smaller than the native page size/ 75 // 76 77 EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize; 78 do { 79 *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress; 80 StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize; 81 } while (StartAddress != EndAddress); 82 83 return; 84 85 } else { 86 ExRaiseAccessViolation(); 87 } 88 89 } else { 90 ExRaiseDatatypeMisalignment(); 91 } 92 } 93 94 return; 95 }
1 VOID 2 ProbeForRead( 3 __in_bcount(Length) VOID *Address, 4 __in SIZE_T Length, 5 __in ULONG Alignment 6 ) 7 8 /*++ 9 10 Routine Description: 11 12 This function probes a structure for read accessibility and ensures 13 correct alignment of the structure. If the structure is not accessible 14 or has incorrect alignment, then an exception is raised. 15 16 Arguments: 17 18 Address - Supplies a pointer to the structure to be probed. 19 20 Length - Supplies the length of the structure. 21 22 Alignment - Supplies the required alignment of the structure expressed 23 as the number of bytes in the primitive datatype (e.g., 1 for char, 24 2 for short, 4 for long, and 8 for quad). 25 26 Return Value: 27 28 None. 29 30 --*/ 31 32 { 33 34 PAGED_CODE(); 35 36 ASSERT((Alignment == 1) || (Alignment == 2) || 37 (Alignment == 4) || (Alignment == 8) || 38 (Alignment == 16)); 39 40 if (Length != 0) { 41 if (((ULONG_PTR)Address & (Alignment - 1)) != 0) { 42 ExRaiseDatatypeMisalignment(); 43 44 } else if ((((ULONG_PTR)Address + Length) > (ULONG_PTR)MM_USER_PROBE_ADDRESS) || 45 (((ULONG_PTR)Address + Length) < (ULONG_PTR)Address)) { 46 47 *(volatile UCHAR * const)MM_USER_PROBE_ADDRESS = 0; 48 } 49 } 50 }
以分頁來管理內存,以頁為單位,如果一個內存頁的第一個字節可寫,那么整個內存頁就可寫,所以就驗證頁的一個字節就可以了。(xxx & ~(PageSize - 1)) + PageSize就是以頁為單位移動。這里也可以看到字節對齊Alignment僅僅是起到了一個驗證的作用,而讀寫驗證也只是一個簡單的指針操作*(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;