Windows驅動跑在核心態(Kernel mode),驅動的調用者跑在用戶態。如何使用戶態進程與核心態驅動共享內存呢 ?
我們知道32位Windows中,默認狀態下虛擬空間有4G,前2G是每個進程私有的,也就是說在進程切換的時候會變化,后2G是操作系統的,所以是固定的。既然用戶態進程和核心態驅動在同一個進程空間里,是不是只要直接傳個內存地址過來,就可以訪問了?理論上可以但實際上不行,因為用戶態的進程在不斷地切換,使驅動運行時沒法保證前面的用戶態進程是哪個,也就不確定前2G虛擬地址空間的映射情況,那么用戶態進程傳來的地址也許不是合法的。
比較常用的做法是通過MDL進行內存的重映射。簡單地說就是將同一塊物理內存同時映射到用戶態空間和核心態空間。
具體來說,可以有兩種做法:用戶態進程分配空間,內核態去映射。另一種是內核態分配空間,用戶態進程去映射。
前者偽碼:
// assume uva is a virtual address in user space, uva_size is its size
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID kva = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
// use kva
// …
MmUnlockPages(mdl);
IoFreeMdl(mdl);
*記得在driver unload之前把mdl unlock和free掉,否則會BSoD。
后者偽碼:
PVOID kva = ExAllocatePoolWithTag(NonPagedPool, 1024, (ULONG)'PMET');
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmBuildMdlForNonPagedPool(mdl);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID uva = MmMapLockedPagesSpecifyCache(mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
*如果kva是分配在nonpagedpool,那這些物理頁本身就是被lock住的,因此用的是MmBuildMdlForNonPagedPool,如果是分配在paged pool里的用MmProbeAndLockPages。
除了這種最原始的方式,Windows還提供了兩種稱為DO_BUFFERED_IO 和DO_DIRECT_IO的方式,前者中系統自動將用戶態空間內存拷貝到了到核心態空間(Associated-Irp.SystemBuffer),后者由系統自動生成MDL(Irp->MdlAddress)。其實這兩種方法本質都是系統幫忙做了上面的部分流程,從而可以讓程序員省了那些操作。
前面提到了一個關鍵數據結構MDL(memorydescriptor list ),系統用它來描述虛擬空間對應物理內存的layout。MDL分為兩部分:固定長部分和變長部分,固定長部分結構如下:
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
Next: 指向下一個MDL結構,從而構成鏈表,有時一個IRP會包含多個MDL
Size:MDL本身的大小,注意包含了定長部分和變長兩部分的size
MdlFlags:屬性標記,如所描述的物理頁有沒有被lock住等
Process:顧名思義,指向該包含該虛擬地址的地址空間的對應進程結構
MappedSystemVa:內核態空間中的對應地址
StartVa:用戶或者內核地址空間中的虛擬地址,取決於在哪allocate的,該值是頁對齊的
ByteCount:MDL所描述的虛擬地址段的大小,byte為單位
ByteOffset:起始地址的頁內偏移,因為MDL所描述的地址段不一定是頁對齊的
如allocate出來的虛擬地址為0xac004010,則StartVa為0xac004000,ByteOffset為0x10,MmGetMdlVirtualAddress給出StartVa + ByteOffset。
變長部分包含了物理頁編號數組,可以用
PPFN_NUMBER pfn = MmGetMdlPfnArray(mdl)
來得到,注意里面只包含了pfn,不包含頁內偏移量。數組的元素個數可以由ADDRESS_AND_SIZE_TO_SPAN_PAGES得到。
jpg 改 rar