MemCheck Driver Memory Tool

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.

Downloads

Download source – 28.8 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read