MDL的使用简介

锁定内存页的意思是:被锁定内存页会一直存在于物理内存中,不会被置换到磁盘文件中去。


MDL只能在内核态使用,但它指定的虚拟内存即可以是内核态地址也可以是用户态地址。如果是用户态的地址你必须要自行弄清地址所在的进程上下文,因为不同的进程拥有不同的地址空间,即使地址的值一模一样它们包含的数据也一定完全不同。如果是内核态的地址那么事情会变的稍微简单点,因为在内核态地址空间是共享的,同一个地址里包含的数据一定是一样的。

MDL本身的结构在DDK里有写,但它属于undocument结构,也就是说微软想改就改不需要事先通知你,所以你最好不要对它做任何假设。不过看一眼当然是没有问题的,又不会怀孕。以下就是MDL数据结构的定义:

// An MDL describes pages in a virtual buffer in terms

// of physical pages. The pages associated with the

// buffer are described in an array that is allocated

// just after the MDL header structure itself.

//

// One simply calculates the base of the array by

// adding one to the base MDL pointer:

//

// Pages = (PPFN_NUMBER) (Mdl + 1);

//

// Notice that while in the context of the subject

// thread, the base virtual address of a buffer mapped

// by an MDL may be referenced using the following:

//

// Mdl->StartVa | Mdl->ByteOffset

//

typedef struct _MDL {

    struct _MDL *Next;

    CSHORT Size;

    CSHORT MdlFlags;

    struct _EPROCESS *Process;

    PVOID MappedSystemVa;

    PVOID StartVa;

    ULONG ByteCount;

    ULONG ByteOffset;

} MDL, *PMDL;

从注释中我们可以看到MDL实际上是一个变长数据结构,在这个结构后面会跟一个数组,把映射到的物理内存的地址都记录下来。而虚拟地址的信息则记录在StartVa中,ByteCount表征它的大小,ByteOffset表征它在页内的偏移。想获得正确的虚存地址,你得用类似 Mdl->StartVa | Mdl->ByteOffset 的方法去获得。想要使用MDL之前你必须先申请一个MDL数据结构。如上面所说MDL是一个变长结构,你不能光申请一个struct _MDL就完事了,你需要自行计算后面所跟数组的大小,以及里面各个域的值,考虑到还有页对齐等一系列恶心烦人的事,我建议你不要手动创建struct _MDL,而是用IoAllocateMdl函数来帮你做这些事情。IoAllocateMdl函数的定义如下:

PMDL

IoAllocateMdl(

IN PVOID VirtualAddress,

IN ULONG Length,

IN BOOLEAN SecondaryBuffer,

IN BOOLEAN ChargeQuota,

IN OUT PIRP Irp OPTIONAL

);

第一个参数为虚存地址;第二个参数为虚存大小;第三个参数与最后一个参数配合使用,如果你在调用IoAllocateMdl时指定了一个IRP,并且SecondaryBuffer为TRUE,那么这个函数会自动把新生成的MDL附加到IRP的MDL列表的最后,如果指定了IRP并且SecondaryBuffer为FALSE那么这个函数会把Irp->MdlAddress设置为新生成的MDL;ChangeQuota一般为FALSE,只有那些会生出新的IRP并往下传的顶层driver才会把它置为TRUE.

值得注意的是IoAllocateMdl函数就跟它的名字一样只负责分配数据结构所需的内存,真正把虚存和物理内存绑在一起的工作它是不负责的,后续工作由另外一批函数负责,比如检测权限和锁定物理内存不让别人占用等事情就由MmProbeAndLockPages函数来完成。这个函数的定义如下:

VOID

MmProbeAndLockPages (

__inout PMDL MemoryDescriptorList,

__in KPROCESSOR_MODE AccessMode,

__in LOCK_OPERATION Operation

);

第一个参数为刚刚生成的MDL,第二个参数指定是用户态的虚存还是内核态的虚存,第三个参数指定访问权限,有IoReadAccess, IoWriteAccess, 和 IoModifyAccess 三种类型可选(事实上 IoWriteAccess和IoModifyAccess 是一模一样的...)。

聪明的同学已经发现问题了:所谓检测权限,那必然是有成功有失败,这个函数怎么不返回任何错误码呢,难道这些个AccessMode的参数传进去都是装装样子的,其实什么事也没做?答案是在权限匹配失败的情况下,MmProbeAndLockPages函数会抛异常,你必须用__try __except之类的SEH关键字给它包起来。在这里我又不得不吐下槽了:真的有好好设计过吗你们?考虑到MSDN关于这块内容的文档质量,再加上这些奇葩的API设计,我怀疑这堆东西根本就是后面打补丁打上去的,而且项目截止日期一定是国庆长假前一天。

事情做到这儿一般来讲已经差不多了,你已经获得了一块永远不会被page out,永远跟指定的虚拟内存一一对应的物理内存。good job!祝你用的开心。假如你生性事多“一般”情况满足不了你(对不起我不该这么说,因为需求永远是多变的),下面还有个函数可以给你点新内容:MmMapLockedPagesSpecifyCache函数可以让你从指定的MDL里生成出新的虚拟地址空间来。假设你的MDL在某个进程上下文里生产,但主要使用场所却是别的进程或是没有进程上下文的地方(比如DPC,内核线程等),那么这个函数会很有用,因为进程切换后或者压根没进程的话,同一个虚拟地址代表的内容是不一样的。

MmMapLockedPagesSpecifyCache函数的定义如下

PVOID

MmMapLockedPagesSpecifyCache (

__in PMDL MemoryDescriptorList,

__in KPROCESSOR_MODE AccessMode,

__in MEMORY_CACHING_TYPE CacheType,

__in_opt PVOID RequestedAddress,

__in ULONG BugCheckOnFailure,

__in MM_PAGE_PRIORITY Priority

);

大家也看到了,返回值是PVOID类型,也就是新的虚拟地址,如果你指定了RequestedAddress,那么返回值跟这个参数应该是一样的,当然,系统也可能没办法满足你指定的地址,在这种情况下假如BugCheckOnFailure参数为TRUE,那么系统就立刻BSOD了。AccessMode可以指定为KernelMode或者UserMode,如果是UserMode,那么该函数会把MDL映射到用户态地址空间去,用户态程序甚至可以直接读取内核态的数据。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值