Before, my blog post about MDLs was a simple link back to Vir Gnarus’ tutorial on MDLs. This time I’m going to write a proper blog post/article about MDLs. I’ll try and expand on the previous tutorial on Sysnative, but will also add some of the original aspects to this blog post.
Please note, I’ll be using the dump file provided on the tutorial on Sysnative.
We should all understand the differences between virtual memory and physical memory. Virtual Memory is always contiguous, whereas, physical memory is discontigous it tends to be more random. This brings in the purpose of a MDL. MDLs are primarily used for I/O operations, whereby, a virtual memory data buffer is locked against a physical address range, the MDL is used to describe the mapping and association between the buffer and the physical address range.
The specific type of I/O operation which this is used for is Direct I/O. Direct I/O is used to deliver IRPs without the need for the use of the system buffer. For Direct I/O, the user’s virtual address buffer of the program is locked into physical memory, thus making the virtual memory buffer non-paged. Once the IRP has finished being processed, through the necessary device objects and associated driver objects in the IRP’ stack, then the I/O Manager will unlock the virtual memory buffer, and then deallocate and free the MDL. You may notice issues and bugchecks of drivers unlocking and locking MDLs too many times.Just to add, that the virtual memory buffer is question, can be User Mode or Kernel Mode virtual memory.
MDLs may also be used in DMA (Direct Memory Access) to describe the referenced physical memory addresses. DMA is used to access RAM without the need of the CPU. There is a number of different methods and issues surrounding this, which will most likely formulate into a blog post.
Looking at the structure of the IRP, in the Fixed Part section, we can see the MDL Address being used. We can also view the structure of an IRP within WinDbg.
I won’t go into the details of the IRP data structure, since it’s not relevant to the topic of this post. It should be fully documented within the WDK. I’ll explain the MDL field since it’s relevant to this topic.
When the IRP, has been sent to the driver (assuming access checks are okay), then the buffer size is checked, if the buffer size is too large for the the proportion of memory, then the IRP is completed by the I/O Manager with an error status and the operation is not continued. On the other hand, if the access checks are fine and the data buffer is of the correct size, then the data buffer will be locked for the entire lifetime of the IRP.
Now, let’s look at the general structure of a MDL. We can view it in Windbg.
The most interesting sections will be the Flags section and the Next section. The Next section is used to chain a number of MDLs together for one virtual data buffer which isn’t contiguous.
The most vital part of this data structure is the MdlFlags field which is defined within the wdk.h header file.
These flags are useful for debugging, since they will define how a MDL is being used, and what APIs should be used with the MDL.
The StartVa field indicates the starting page aligned for the virtual address range. (MDL_MAPPED_TO_SYSTEM_VA and/or MDL_SOURCE_IS_NONPAGED_POOL flags must be set; VA usually means Virtual Address).
The ByteCount field indicates the entire length of the virtual address range mapped by the MDL.
ByteOffSet is the first address offset for the first page of the MDL.
The Process field contains a pointer to the _EPROCESS data structure of the process object, whose virtual address space is mapped by the MDL.
The Size field contains the size of MDL data structure and a array of the PFNs used for the physical page.