Environment: Windows NT/2K/XP driver development
Purpose
The MemCheck code is designed to provide Windows NT/2K/XP driver developers
with a tool to help in the detection of the following memory handling issues:
Buffer overrun
Buffer corruption
Buffer use after buffer release
Double buffer releases
MemCheck’s Routines
MemCheck has a specific API (set of functions) that should be used in place
of existing routines.
Original routine |
MemCheck routine |
ExAllocatePool(…) | MEM_ALLOCATE(…) |
ExAllocatePoolWithTag(…) | MEM_ALLOCATE_WITH_TAG(…) |
ExFreePool(…) | MEMCHECK_FREE(…) |
The parameters for each of the MemCheck routines are exactly the same as those
in the original (DDK) functions. By replacing the original calls with the memCheck
versions, validation can be done during the allocation and release routines.
In addition to the functions listed above, MemCheck also has the following
routines:
MemCheck Routine | Description |
MEM_CHECK_INIT() | Initialize memCheck’s resources. This routine should be called before any other memCheck routines are used. |
MEM_CHECK() | Forces memCheck to validate all buffers being monitored. |
MEM_LIST_DISPLAY() |
Displays a list of all buffers being monitored by memCheck. In addition, each buffer being monitored is validated for correctness (the header and footer tags are checked). |
MEM_VALID_ACCESS() | Tests ptr to see if it points to a location within a valid buffer being monitored by memCheck. |
MEM_CHECK_EXIT() | Frees resources used by memCheck. This routine should be called when unloading. Make sure not to make further calls to memCheck routines after calling MEM_CHECK_EXIT() |
MemCheck Settings
The settings for MemCheck are contained in the memcfg.h file. Each of these
settings is a simple macro. By defining and undefining a macro, the setting
can be turned on or off.
MemCheck Setting |
Description |
MEMCHECK_ON | Enable/disable the entire memcheck package. |
MEMCHECK_DEBUGPRINT |
Enables displaying of MEMCHECK_DEBUG() macro information this is mostly debug information for memCheck itself. |
MEMCHECK_DISPLAYPRINT | Enables displaying of MEMCHECK_PRINT() macro information |
MEMCHECK_DISPLAY_FREES | Display information when buffer is freed |
MEMCHECK_DOUBLE_FREES | Enable checking for double frees…this causes extra memory to be used and should NOT be left enabled for a shipping product. |
MEMCHECK_HALT_ON_BAD | Causes a debug breakpoint to occur when a bad buffer tag is encounted |
MEMCHECK_FREE_ON_EXIT | Allows memcheck to free its resources upon exit. This is normally left on |
How MemCheck works
MemCheck’s main functionality is exercised when all allocation and release
routines (ExAllocatePool(…), ExAllocatePoolWithTag(…) and ExFreePool(…))
are replaced with the MemCheck analog (see MemCheck’s Routines above for
more information). With these routines in place, MemCheck maintains a list of
each buffer allocated. When the client requests a 1K buffer be allocated, MemCheck actually allocates
a larger buffer. This extra space is used to store tags at the start (header)
and end (footer) of the buffer. The client is returned a pointer to a buffer of the requested size (assuming
the call was successful). MemCheck then writes a specific value in the header
and footer tags. If at any point MemCheck realizes that the tags are invalid,
it can tell that the user has perturbed their memory.
NOTE: MemCheck stores more information within the buffer than just the tags
and client data. The above description is a simplification of the actual method
MemCheck uses. Please refer to memcheck.c code to see how the real buffer list
is maintained.
Another useful feature of MemCheck is MEM_VALID_ACCESS(). By calling MEM_VALID_ACCESS(ptr),
MemCheck can compare the ptr address to the addresses of each of the monitored
buffers. If the address is not within a valid buffer range, MemCheck can display
a warning.
When the MEMCHECK_DOUBLE_FREES macro is defined (in memcfg.h) and the client
frees a buffer, the buffer is not actually released. Instead, it’s information
is maintained. In the event that an attempt is made to free the same ptr, MemCheck
will be able to notify the client.
Using the MemCheck Code
The first step in using the MemCheck code is to call MEM_CHECK_INIT() when
your driver initializes and MEM_CHECK_EXIT() when your driver unloads. The next
step is to replace all the original DDK memory allocation and release calls
with the MemCheck counterparts (see MemCheck’s Routines above for more
information). After completing these steps, make sure MemCheck is enabled
(via the MEMCHECK_ON macro in memcfg.h). Then build the driver and run it. MemCheck
will be able to scour the memory usage right away and try to find problems.
In addition to the previous steps, users may find it useful to use the other
‘helper’ functions to track down buffer problems. These functions
are
- MEM_CHECK()
- MEM_LIST_DISPLAY()
- MEM_VALID_ACCESS()
(see MemCheck Routines above for a description of these routines).
When you have finally tracked down all memory issues, you can disable the MemCheck
package by undefining the MEMCHECK_ON macro in memcfg.h. This has the effect
of making your code build with no code from MemCheck being included. This means
that the size and performance of the driver will be the same as one using only
the DDK calls.