当前位置 博文首页 > KOOKNUT的博客:浅谈MDL(壹)(Windows内核学习笔记)

    KOOKNUT的博客:浅谈MDL(壹)(Windows内核学习笔记)

    作者:[db:作者] 时间:2021-07-02 21:33

    先说说MDL是个啥东西,看一下MSDN的定义:
    Memory descriptor list. An opaque structure, defined by the Memory Manager, that uses an array of physical page frame numbers (PFNs) to describe the pages that back a virtual memory range.
    内存描述符表。由内存管理器定义的一个不透明结构体,使用物理页帧号数组来描述支持虚拟内存的页。
    听起来可高大上的一个解释,先来看看它的定义,分别在Windbg和**中的定义:

    //Win10下的定义
    //0x1c bytes (sizeof)
    struct _MDL
    {
        struct _MDL* Next;     		//0x0	MDL队列中的下一个成员
        SHORT Size;            		//0x4	整个MDL列表的长度,包括MDL结构体和物理页帧号所占的内存 物理页帧号起始地址紧跟在结构体后
        SHORT MdlFlags;        		//0x6	标志,设置一些内存属性
        struct _EPROCESS* Process;	//0x8	缓冲区所属进程
        VOID* MappedSystemVa;    	//0xc	映射之后的系统空间虚拟地址
        VOID* StartVa;       	 	//0x10	缓冲区所在第一个页面的虚拟地址
        ULONG ByteCount;       		//0x14	字节数,虚拟地址的大小
        ULONG ByteOffset;     	 	//0x18	StartVa + ByteOffset缓冲区开始的地址
    }; 
    //Win7的Windbg版本
    0: kd> dt nt!_MDL
       +0x000 Next             : Ptr32 _MDL
       +0x004 Size             : Int2B
       +0x006 MdlFlags         : Int2B
       +0x008 Process          : Ptr32 _EPROCESS
       +0x00c MappedSystemVa   : Ptr32 Void
       +0x010 StartVa          : Ptr32 Void
       +0x014 ByteCount        : Uint4B
       +0x018 ByteOffset       : Uint4B
    

    好像在Win7和Win下的定义,并没有什么变化。。。
    为什么要使用MDL?
    我们都知道在内核中运行的代码,可以访问用户层的内存空间,但是当线程调度发生,当前的页面映射表不再是之前的用户层进程的页面映射,但是CPU仍然访问原本的用户层虚拟地址,就会发生不可预知的错误,因为用户层的低2G虚拟空间空间是独立的。也就是说,我们访问到的地址可能并不是真正的目标地址。解决办法之一,就是使用MDL映射一份对应的物理内存到系统空间的虚拟地址中来,并且被MDL锁定之后,页面不会被换出。MDL只能在内核态使用,它可以指定对内核虚拟地址或者用户虚拟地址的映射。
    接下来说一下MDL的使用:
    对于很小的缓冲区来说,使用MDL不太划算,毕竟建立和撤销一个新的映射需要一定的开销,对于大一点的缓冲区可以使用。
    之前我说过的一个代码,用的是内核态映射用户态的虚拟地址,使用MDL进行缓冲区数据拷贝。看一些常用的MDL操作函数:

    //该宏未初始化MappedSystemVa,因为还没有进行映射
    #define MmInitializeMdl(_MemoryDescriptorList, \
                            _BaseVa, \
                            _Length) \
    { \
      (_MemoryDescriptorList)->Next = (PMDL) NULL; \
      (_MemoryDescriptorList)->Size = (CSHORT) (sizeof(MDL) + \
        (sizeof(PFN_NUMBER) * ADDRESS_AND_SIZE_TO_SPAN_PAGES(_BaseVa, _Length))); \
      (_MemoryDescriptorList)->MdlFlags = 0; \
      (_MemoryDescriptorList)->StartVa = (PVOID) PAGE_ALIGN(_BaseVa); \
      (_MemoryDescriptorList)->ByteOffset = BYTE_OFFSET(_BaseVa); \
      (_MemoryDescriptorList)->ByteCount = (ULONG) _Length; \
    }
    PMDL
    NTAPI
    IoAllocateMdl(IN PVOID VirtualAddress,
                  IN ULONG Length,
                  IN BOOLEAN SecondaryBuffer,
                  IN BOOLEAN ChargeQuota,
                  IN PIRP Irp)
    {
        PMDL Mdl = NULL, p;
        ULONG Flags = 0;
        ULONG Size;
    
        /*断言长度有效*/
        ASSERT(Length != 0);
    
        /*超过地址空间的一半,肯定无法映射,因为MDL存在于内核中,且不能被换出*/
        if (Length & 0x80000000) return NULL;
    
        /*计算缓冲区跨过的页面数量*/
        Size = ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, Length);
        if (Size > 23)
        {
            /*超过23个页面,根据实际大小估算MDL的大小*/
            Size *= sizeof(PFN_NUMBER);//sizeof(PFN_NUMBER)物理页帧号的大小
            Size += sizeof(MDL);
            if (Size > MAXUSHORT) return NULL;
        }
        else
        {
            /*不超过23,按照标准大小23计算大小*/
            Size = (23 * sizeof(PFN_NUMBER)) + sizeof(MDL);
            Flags |= MDL_ALLOCATED_FIXED_SIZE;
    
            /*Lookaside防止内存空洞,相当于一个内存管理器一样*/
            Mdl = IopAllocateMdlFromLookaside(LookasideMdlList);//现成的数据结构分配内存,效率较高
        }
    
        /**/
        if (!Mdl)
        {
            /*若失败,非分页内存池中申请内存*/
            Mdl = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_MDL);
            if (!Mdl) return NULL;
        }
    
        /*初始化MDL*/
        MmInitializeMdl(Mdl, VirtualAddress, Length);
        Mdl->MdlFlags |= Flags;
    
        /* Check if an IRP was given too */
        if (Irp)
        {
            /* Check if it came with a secondary buffer */
            if (SecondaryBuffer)
            {
                /* Insert the MDL at the end */
                p = Irp->MdlAddress;
                while (p->Next) p = p->Next;
                p->Next = Mdl;
          }
          else
          {
                /* Otherwise, insert it directly */
                Irp->MdlAddress = Mdl;
          }
       }
    
        /* Return the allocated mdl */
        return Mdl;
    }          
    

    今天篇幅已经够长的了,下一次我们再分析剩下的常见MDL函数
    MmProbeAndLockPages
    MmMapLockedPagesSpecifyCache
    MmUnmapLockedPages
    MmUnlockPages
    IoFreeMdl
    “When love and duty are one, then grace is within you.
    当爱和责任合二为一,恩典便与你同在。”
    参考书籍:
    《Windows内核情景分析》

    cs