Bugs are the coolest, most exciting things in the whole wide world! If you are an entomologist, you just might agree with that statement. Any competent software engineer—and I am 99.999 percent sure that you are since you’re reading this—would probably wonder what kind of drugs I’m on. Granted, bugs are definitely not cool when they cause you to stay up all night trying to figure them out, or worse yet, when they cause you to lose your job because too many customers send a buggy product back. Proactively finding and fixing bugs long before your product gets to the customer can be very satisfying—especially since you won’t have to spend the last half of the development cycle sitting in a debugger wondering where the problems are.
Welcome to The Bugslayer! This article introduces my new column in MSJ. In my column, I will discuss ways that you can find and fix as many bugs as possible before the customer ever sees your product. Since bugs can creep into your product anywhere from design to final ship, there is a great deal of ground to cover. My goal is to provide information that will help you build automatic bug killing right into your products. While a few columns might contain higher-level discussions, most will focus on particular areas where bugs are found—for example, multithreading. And following the MSJ tradition, they will offer tools, techniques and a bunch of source code you can use.
Now that I have broadly outlined the column, here is where you come in. I want to cover the topics you are most interested in seeing, so let me know what your interests are. While some bug-solving techniques are generic across all environments, others aren’t. I want to try and hit those that are most interesting to the most folks. If you have debugging or bug-squashing questions or ideas, drop me a line at john@jprobbins.com. I look forward to hearing from you.
When Visual C++® 4.0 first arrived on my doorstep, it had all of those nice new features, but in my mind one small little addition was coolest: the Debug Run-Time Library. The strange thing is, most folks don’t seem to realize that it exists because much of it is turned off by default. Once you get the proper flags switched on, however, it offers many great features: memory overwrite, underwrite, and freed memory access checking; memory leak checking; memory allocation hook; user-defined memory block dumping; and clean asserting and reporting macros. Plus, many of the features are extensible! In this article, I will extend the debug runtime so that you get even more functionality out of it.
The first library I developed, MemDumperValidator, provides a generic mechanism for hooking into the memory-dumping part of the debug runtime. So, when there is an error with your allocated memory, you get to see exactly what was in that memory. It also sets up a scheme to allow you to validate everything inside a memory block. The second library I developed, MemStressLib, hooks into the allocation portion of the debug runtime so that you can selectively fail memory allocations and test how your program handles them.
Since Microsoft is nice enough to provide the complete runtime source code, all the functionality for the debug runtime is easily seen. In the CRT\SRC directory, all the work takes place in the following files:
DBGDEL.CPP | The debug global delete operator. |
DBGHEAP.C | All of the debug heap-handling functions. |
DBGHOOK.C | The stub memory allocation hook function. |
DBGINT.H | The internal debug headers and functions. |
DBGNEW.CPP | The debug global new operator. |
DBGRPT.C | The debug reporting functions. |
CRTDBG.H | The header file you include. This is in the standard include directory. |
The debug runtime is full of features, but I want to concentrate on the memory tracking and checking features that it offers. The first step to using the debug runtime is to include the main header CRTDBG.H, where all the functionality is defined. Right before you do the actual include, you will need to define _CRTDBG_MAP_ALLOC. This gets the allocation routines mapped to special versions that record the source and line of the call, which really helps give you some extra information about where things happened.
After you get the debug runtime included, you have to get it turned on. The documentation says that most of the debug runtime is turned off to keep the code small and to increase execution speed. While this may be important for a release build, the whole point of a debug build is to find bugs! The increased size and reduced speed of debug builds is inconsequential. The _CrtSetDbgFlag function takes a set of flags, shown in Figure 1, that turns on various options in the debug runtime. If you want to use either of the libraries, I included CRTDBG.H and defined _CRTDBG_MAP_ALLOC for you. Both libraries make it a snap to get the full debug runtime library turned on.
Figure 1: Debug Runtime Flags
Flag | Meaning |
_CRTDBG_ALLOC_MEM_DF | Turn on the debug heap allocations and use the memory block identifiers. This is the only flag that’s on by default. |
_CRTDBG_CHECK_ALWAYS_DF | Check and validate all memory on each allocation and deallocation request. Setting this flag on is what catches the under and overwrites so it is very important to get it turned on. |
_CRTDBG_CHECK_CRT_DF | Include _CRT_BLOCK memory allocations in all leak detection and state differences. |
_CRTDBG_DELAY_FREE_MEM_DF | Instead of truly freeing memory, keep the block allocated and in the internal heap list. The blocks are filled with the value0xDD so you know the memory is freed when looking at it in the debugger. By also not freeing the memory, this can help provide stress conditions for the program. |
_CRTDBG_LEAK_CHECK_DF | Do memory leak checking at the end of the program. |
Now that you have the debug runtime fully available, you get a slew of functions that really help you control memory usage. One of the most useful functions that you can call is _CrtCheckMemory. This function walks through all of the memory you have allocated and checks to see if you have any underwrites or overwrites and if you have used any blocks that were previously freed. This one function alone makes the entire debug runtime worth using.
But wait, there’s more! Another set of functions allows you to easily check the validity of any piece of memory. The _CrtIsValidHeapPointer, _CrtIsMemoryBlock, and _CrtIsValidPointer functions are perfect for using as debugging parameter validation functions. If they are wrapped in ASSERT macros, they become doubly useful. While these, combined with _CrtCheckMemory, offer sufficient memory checking, there’s still more!
Another neat feature of the debug runtime is the memory state routines. _CrtMemCheckpoint, _CrtMemDifference, and _CrtMemDumpStatistics make it easy to do before and after comparisons of the heap to see if anything is amiss. For example, if you are using a common library in a team environment, you could take before and after snapshots of the heap when you call the library to see if there are any leaks, or to see how much memory is used on the operation.
The icing on the memory-checking cake is that the debug runtime allows you to hook into the memory allocation code stream so you can see each allocation and deallocation function call. If the allocation hook returns TRUE, the allocation is allowed to continue. If the allocation hook returns FALSE, then the allocation will fail. My immediate thought was that, with a small amount of work, I could have a means to test code in some really nasty boundary conditions that would be very difficult to duplicate. Fortunately, you will not have to do that work because it’s handled by MemStressLib, one of the libraries that I will present later.
The cherry on top of the icing of the memory-checking cake is that the debug runtime allows you to hook the memory dumping routines and to enumerate client blocks (your allocated memory). With the memory dumping, you can now hook in a dump routine that knows about your data, so that instead of seeing the default cryptic dumped memory, which is not very helpful, you can see exactly what the memory block contains and format it exactly as you want. MFC has the Dump function for this purpose, but it only works with CObject-derived classes. If you’re like me, you don’t spend your entire coding life in MFC and you need dumping functions that are more generic to accommodate different types of code.
The client enumeration, as the name implies, allows you to enumerate the memory blocks you have allocated. This means you have an excellent opportunity for some interesting utilities. In the MemDumperValidator library, I combined the dumping hooks so the enumeration can dump and validate many types of allocated memory. The validation is very important when you consider that this extensible validation allows you to do “deep” validation instead of the surface checks of underwrites and overwrites. By deep, I mean something that knows about what is in the memory block so it can truly make sure that everything
is correct.
I have just highlighted the debug runtime here so you’ll more easily understand the libraries that I am about to present. There is a great deal of value in the debug runtime, and the best part is that, in conjunction with the MemStress and MemDumperValidator libraries, you get all of your cake and a fork to help you eat it. But before I jump right into the code, I need to explain a little bit about how things are initialized in Visual C++®.
In my MemDumperValidator library, I take advantage of a small trick to get everything initialized by the compiler long before you use the library, and terminated long after the program is finished executing your code. While I could have you call initialization and shutdown functions to tell MemDumperValidator to start and stop, your calls could happen too late and too early, respectively, if you have any static C++ classes that use MemDumperValidator. Static C++ classes are constructed before main/WinMain is called and destructed after main/WinMain returns, so it might be rather difficult for you to figure out when to do the initialization and shutdown calls. My goal is to make the library as automatic as possible so you can just use it without spending a lot of time figuring it out. Also, controlling the initialization order is something that you do not think about much, but when it is wrong it takes some serious debugging effort to get right.
What I need is a way to tell the compiler to call my initialization routines before it calls the ones in my code, and to call my termination routines after it calls those in my code. The documentation refers to this as the initialization order; the #pragma init_seg is how you can control it. There are several “parameters” that you can pass to the init_seg directive: compiler, lib, user, section name, and func-name. The first three are the important ones.
The compiler directive is reserved for the Microsoft compiler, and any objects specified for this group are constructed first and destructed last. Those that are marked as lib are constructed next and destructed before the compiler-marked group, and those marked user are constructed last and terminated first.
Since the code that I developed needs to be initialized before your code, I could just specify lib as the directive to init_seg and be done with it. However, if you are creating libraries and marking them as lib segments (as you should) and want to use my code, my code still needs to be initialized before your code. To handle this contingency, I set the init_seg directive as “compiler.” While I would not suggest you do this with release-build code, it is safe enough with debug code.
Since the initialization idea only works with C++ code, MemDumperValidator uses a special static class that simply calls the initializer functions for the libraries. The initializer function is a little more complex than just setting a couple of variables to known values, which is why I need to go to all of this trouble. Additionally, as discussed below, to get around some limitations in the debug runtime memory leak checking, I need to do some special processing on the class destruction. Even though the library only has a C interface, I can take advantage of C++ to get everything lined up so it is ready when you call it.
As I mentioned earlier, the stock memory dumping routines could be improved because they just display the first couple of bytes of the block and the address. The MemDumperValidator library allows you to hook into the debug runtime memory dumping; you can get nicely formatted output of exactly what is in your memory, so you have an idea what is in the block you are leaking or corrupting. When debugging, any extra piece of information is power.
In addition to memory dumping, the validator portion gives you the means not only to check for overwrites and underwrites, but a clean way to do deep validation of everything in the memory block. Before I delve into the inner workings, I will discuss the high-level view and how to use the library. If you want to follow along with the code, Figure 2 shows the header file MemDumperValidator.h.
Figure 2: MemDumperValidator.h
/*----------------------------------------------------------------------
John Robbins
Microsoft Systems Journal, October 1997 - Bugslayer!
----------------------------------------------------------------------*/
#ifndef _MEMDUMPERVALIDATOR_H
#define _MEMDUMPERVALIDATOR_H
// Include the main header.
#include "MSJDBG.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// This library can only be used in _DEBUG builds.
#ifdef _DEBUG
////////////////////////////////////////////////////////////////////////
// The typedefs for the dumper and validator functions.
////////////////////////////////////////////////////////////////////////
// The memory dumper function. The only parameter is a pointer to the
// block of memory. This function can output the memory data for the
// block any way it likes but it might be nice if it uses the same
// Debug CRT reporting mechanism that everything else in the runtime
// uses.
typedef void (*PFNMEMDUMPER)(const void *) ;
// The validator function. The first parameter is the memory block to
// validate and the second parameter is the context information passed
// to the ValidateAllBlocks function.
typedef void (*PFNMEMVALIDATOR)(const void * , const void *) ;
////////////////////////////////////////////////////////////////////////
// Useful Macros.
////////////////////////////////////////////////////////////////////////
// The macro used to set a client block value. This is the ONLY
// approved means of setting a value for the dwValue field in the
// DVINFO structure below.
#define CLIENT_BLOCK_VALUE(x) (_CLIENT_BLOCK|(x<<16))
// A macro to pick out the subtype.
#define CLIENT_BLOCK_SUBTYPE(x) ((x >> 16) & 0xFFFF)
////////////////////////////////////////////////////////////////////////
// The header used to initialize the dumper and validator for a specific
// type of client block.
////////////////////////////////////////////////////////////////////////
typedef struct tag_DVINFO
{
// The value for the client blocks. This must be set with the
// CLIENT_BLOCK_VALUE macro above. See the AddClientDV function
// for how to have the library assign this number.
unsigned long dwValue ;
// The pointer to the dumper function.
PFNMEMDUMPER pfnDump ;
// The pointer to the dumper function.
PFNMEMVALIDATOR pfnValidate ;
} DVINFO , * LPDVINFO ;
/*----------------------------------------------------------------------
FUNCTION : AddClientDV
DISCUSSION :
Adds a client block dumper and validator to the list. If the
dwValue field in the DVINFO structure is ZERO, then the next value in
the list is assigned. This means that the value returned must always be
passed to _malloc_dbg as the value of the client block.
If the value is set with CLIENT_BLOCK_VALUE, then a macro can be
used for the value to _malloc_dbg.
No, there is no corresponding remove function. Why possibly
introduce bugs in debugging code? Performance is a non issue when it
comes to finding errors.
PARAMETERS :
lpDVInfo - The pointer to the DVINFO structure.
RETURNS :
1 - The client block dumper and validator was properly added.
0 - The client block dumper and validator could not be added.
----------------------------------------------------------------------*/
int AddClientDV ( LPDVINFO lpDVInfo ) ;
/*----------------------------------------------------------------------
FUNCTION : ValidateAllBlocks
DISCUSSION :
Checks all the memory allocated out of the local heap. Also goes
through all client blocks and calls the special validator function for
the different types of client blocks.
It is probably best to call this function with the VALIDATEALLBLOCKS
macro below.
PARAMETERS :
pContext - The context information that will be passed to each
call to the validator function.
RETURNS :
None.
----------------------------------------------------------------------*/
void ValidateAllBlocks ( void * pContext ) ;
#ifdef __cplusplus
////////////////////////////////////////////////////////////////////////
// Helper C++ class macros.
////////////////////////////////////////////////////////////////////////
// Declare this macro in your class just like the MFC ones.
#define DECLARE_MEMDEBUG(classname) \
public : \
static DVINFO m_stDVInfo ; \
static void ClassDumper ( const void * pData ) ; \
static void ClassValidator ( const void * pData , \
const void * pContext ) ; \
void * operator new ( size_t nSize ) \
{ \
if ( 0 == m_stDVInfo.dwValue ) \
{ \
m_stDVInfo.pfnDump = classname::ClassDumper ; \
m_stDVInfo.pfnValidate = classname::ClassValidator ; \
AddClientDV ( &m_stDVInfo ) ; \
} \
return ( _malloc_dbg ( nSize , \
m_stDVInfo.dwValue , \
__FILE__ , \
__LINE__ ) ) ; \
} \
void * operator new ( size_t nSize , \
char * lpszFileName , \
int nLine ) \
{ \
if ( 0 == m_stDVInfo.dwValue ) \
{ \
m_stDVInfo.pfnDump = classname::ClassDumper ; \
m_stDVInfo.pfnValidate = classname::ClassValidator ; \
AddClientDV ( &m_stDVInfo ) ; \
} \
return ( _malloc_dbg ( nSize , \
m_stDVInfo.dwValue , \
lpszFileName , \
nLine ) ) ; \
} \
void operator delete ( void * pData ) \
{ \
_free_dbg ( pData , m_stDVInfo.dwValue ) ; \
}
// Declare this one at the top of the CPP file.
#define IMPLEMENT_MEMDEBUG(classname) \
DVINFO classname::m_stDVInfo
// The macro for memory debugging allocations. If DEBUG_NEW is defined,
// then it can be used.
#ifdef DEBUG_NEW
#define MEMDEBUG_NEW DEBUG_NEW
#else
#define MEMDEBUG_NEW new ( __FILE__ , __LINE__ )
#endif
#endif // __cplusplus defined.
////////////////////////////////////////////////////////////////////////
// Helper C macros.
////////////////////////////////////////////////////////////////////////
// For C style allocations, here is the macro to use. Unfortunately,
// with C it is not so easy to use the auto-increment feature of
// AddClientDV.
#define INITIALIZE_MEMDEBUG(bType , pfnD , pfnV ) \
{ \
DVINFO dvInfo ; \
dvInfo.dwValue = bType ; \
dvInfo.pfnDump = pfnD ; \
dvInfo.pfnValidate = pfnV ; \
AddClientDV ( &dvInfo ) ; \
}
// The macros that map the C-style allocations. It might be easier if
// you use macros to wrap these so you don't have to remember which
// client block value to drag around with each memory usage function.
#define MEMDEBUG_MALLOC(bType , nSize) \
_malloc_dbg ( nSize , bType , __FILE__ , __LINE__ )
#define MEMDEBUG_REALLOC(bType , pBlock , nSize) \
_realloc_dbg( pBlock , nSize , bType , __FILE__ , __LINE__ )
#define MEMDEBUG_EXPAND(bType , pBlock , nSize ) \
_expand_dbg( pBlock , nSize , bType , __FILE__ , __LINE__ )
#define MEMDEBUG_FREE(bType , pBlock) \
_free_dbg ( pBlock , bType )
#define MEMDEBUG_MSIZE(bType , pBlock) \
_msize_dbg ( pBlock , bType )
// Macro to call ValidateAllBlocks
#define VALIDATEALLBLOCKS(x) ValidateAllBlocks ( x )
#else // _DEBUG is NOT defined
#ifdef __cplusplus
#define DECLARE_MEMDEBUG(classname)
#define IMPLEMENT_MEMDEBUG(classname)
#define MEMDEBUG_NEW new
#endif // __cplusplus
#define MEMDEBUG_MALLOC(bType , nSize) malloc ( nSize )
#define MEMDEBUG_REALLOC(bType , pBlock , nSize) \
realloc ( pBlock , nSize )
#define MEMDEBUG_EXPAND(bType , pBlock , nSize) \
_expand ( pBlock , nSize )
#define MEMDEBUG_FREE(bType , pBlock) free ( pBlock )
#define MEMDEBUG_MSIZE(bType , pBlock) _msize ( pBlock )
#define VALIDATEALLBLOCKS(x)
#endif // _DEBUG
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _MEMDUMPERVALIDATOR_H
The MemDumperValidator takes advantage of the debug runtime block identifier capabilities so that it can associate a block type to a set of routines that knows something about what is in the block. After you set up a class or C data type to use the MemDumperValidator library, the library will be called when the debug runtime wants to dump a block. The library will look at the block value, and if there is a matching dumping function, it will call it to dump the memory. The validation portion will do the same thing when called by the debug runtime, except it calls the validation function. As usual, describing it is easy, but getting it all to work is a little more difficult.
Setting up a C++ class so it can be handled by the MemDumperValidator library is a relatively simple operation. In the declaration of the C++ class, just specify the DECLARE_MEMDEBUG macro with the class name as the parameter. This macro is rather like some of the magic MFC macros in that it expands into a couple of data and method declarations. If you are following along in MemDumperValidator.h, you will notice that there are three inline functions: new, delete, and new with placement syntax. If you have any of these three operators in your class, then you will need to extract what these functions do and place it in your code.
In the implementation file for your C++ class, you need to use the IMPLEMENT_MEMDEBUG macro, again with your class name as the parameter. This sets up a static variable for your class. The IMPLEMENT_MEMDEBUG and DECLARE_MEMDEBUG macros only expand in _DEBUG builds, so they do not need to have conditional compilation used around them.
After you have specified both macros in the correct place, you will only need to implement the two functions that will do the actual dumping and validation for your class. The prototypes for those functions are shown below. Obviously, you will want to put some conditional compilation around them so they are not compiled into release builds.
static void ClassDumper ( const void * pData ) ;
static void ClassValidator ( const void * pData,
const void * pContext ) ;
For both functions, the pData parameter is the actual memory block that points to an instance of the class. All that is needed to get to a usable pointer is to cast the value in pData to the class type. Whatever you do when you are dumping or validating, treat the value in pData as super read-only or you could cause yourself major headaches. For the ClassValidator method, the second parameter, pContext, is the context parameter that was passed to the original call to the ValidateAllBlocks function. This is discussed in more detail later.
I have only two recommendations for implementing the dump function. First, stick to the _RPTn macros so that your formatted output will go to the same place as the rest of the runtime debug output. Second, end your output with a carriage return/line feed combination.
While it looks almost trivial to set up a dumper and validator for a C++ class, what about those C data structures that would be nice to finally get dumped cleanly? Unfortunately, it takes a little more work to handle them. To see all the steps in action, follow the C examples in Dump.cpp (see Figure 3).
Figure 3: Dump.cpp
/*----------------------------------------------------------------------
John Robbins
Microsoft Systems Journal, October 1997 - Bugslayer!
----------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <iostream.h>
#include "MemDumperValidator.h"
class TestClass
{
public:
TestClass ( void )
{
strcpy ( m_szData , "TestClass constructor data!" ) ;
}
~TestClass ( void )
{
m_szData[ 0 ] = '\0' ;
}
// The declaration of the memory debugging stuff for C++ classes.
DECLARE_MEMDEBUG ( TestClass ) ;
private :
char m_szData[ 100 ] ;
} ;
// This set up the static DVINFO
IMPLEMENT_MEMDEBUG ( TestClass ) ;
// The functions that you must implement to do the dumping and
// validating.
#ifdef _DEBUG
void TestClass::ClassDumper ( const void * pData )
{
TestClass * pClass = (TestClass*)pData ;
_RPT1 ( _CRT_WARN ,
" TestClass::ClassDumper : %s\n" ,
pClass->m_szData ) ;
}
void TestClass::ClassValidator ( const void * pData ,
const void * )
{
// Do any validation on the data here,
TestClass * pClass = (TestClass*)pData ;
_RPT1 ( _CRT_WARN ,
" TestClass::ClassValidator : %s\n" ,
pClass->m_szData ) ;
}
#endif
typedef struct tag_SimpleStruct
{
char szName[ 256 ] ;
char szRank[ 256 ] ;
} SimpleStruct ;
// The dumper and validator for simple string data memory.
void DumperOne ( const void * pData )
{
_RPT1 ( _CRT_WARN , " Data is : %s\n" , pData ) ;
}
void ValidatorOne ( const void * pData , const void * pContext )
{
// Do any validation on the string data here.
_RPT2 ( _CRT_WARN ,
" Validator called with : %s : 0x%08X\n" ,
pData , pContext ) ;
}
// The dumper and validator for the structure allocations.
void DumperTwo ( const void * pData )
{
_RPT2 ( _CRT_WARN ,
" Data is Name : %s\n"
" Rank : %s\n" ,
((SimpleStruct*)pData)->szName ,
((SimpleStruct*)pData)->szRank ) ;
}
void ValidatorTwo ( const void * pData , const void * pContext )
{
// Do any structure validations here.
_RPT2 ( _CRT_WARN ,
" Validator called with : %s : 0x%08X\n" ,
pData , pContext ) ;
}
// The C functions, unfortunately, need to have predefined values for
// the client blocks. The best bet is to try and make them high
// enough that they stay out of the way. In a real world application,
// these should all be centrally located in a single header file that
// everyone includes.
#define ONE_CB CLIENT_BLOCK_VALUE(10)
#define TWO_CB CLIENT_BLOCK_VALUE(11)
void main ( void )
{
cout << "At start of main\n" ;
// Do the memory debugging initialization stuff for type one.
INITIALIZE_MEMDEBUG ( ONE_CB , DumperOne , ValidatorOne ) ;
// The memory debugging initialization for number two.
INITIALIZE_MEMDEBUG ( TWO_CB , DumperTwo , ValidatorTwo ) ;
// Allocate the class with the memory debugging new.
TestClass * pstClass ;
//pstClass = MEMDEBUG_NEW TestClass ;
pstClass = new TestClass ;
// Allocate the two C types.
char * p = (char*)MEMDEBUG_MALLOC ( ONE_CB , 10 ) ;
strcpy ( p , "VC VC" ) ;
SimpleStruct * pSt =
(SimpleStruct*)MEMDEBUG_MALLOC ( TWO_CB ,
sizeof ( SimpleStruct ) ) ;
strcpy ( pSt->szName , "Pam" ) ;
strcpy ( pSt->szRank , "CINC" ) ;
// Validate all the blocks in the list.
VALIDATEALLBLOCKS ( NULL ) ;
cout << "At end of main\n" ;
// Everything will get dumped as part of the memory leak checking.
}
The first step is to set the unique memory block values for your C data structures. The C++ macros automatically handle this. The specific values that you will want to assign for each individual class must be defined in terms of the CLIENT_BLOCK_VALUE macro, which takes a single integer value as a parameter. If you are going to have several C data structure dumpers and validators, it might be a good idea to define all the memory block values in the same header file since they all must be unique.
While the C++ macros automatically declare the dumper and validator functions for you, you will need to manually declare them for your C memory. The prototypes for the C dumper and validator functions are the same as the C++ versions except that the static keyword is not used. Like declaring the unique memory block values, you might want to consider placing all the C memory dumping and
validation function implementations in a combined file
for convenience.
Before you can start allocating, dumping, or validating C memory, you must tell the MemDumpValidator library about the block type and the dumper and validator functions for it. This is handled with the INITIALIZE_
MEMDEBUG macro that takes the assigned block value, the dump function, and the validate function as parameters. You will need to have the macro executed before you allocate any memory blocks for this type.
Finally—and this is where the C++ memory handling is far better—to allocate, free, reallocate, expand, or get the size of a block with msize, you must use a whole set of macros that pass the block value through to the underlying memory function. For example, if your block value is defined as MYBLOCKVAL, then you need to allocate your C blocks with:
MEMDEBUG_MALLOC(MYBLOCKVAL,sizeof(x))
Figure 2 shows all the different macros for the C memory functions. Since it can get rather confusing to remember the block type for each and every type of allocation, you might want to set up wrapper macros to handle it for you, so that all you need to pass to your wrapper macros are the normal parameters to the memory functions.
While the dumping portion of this library is unquestionably useful, you might be wondering why you need the validation method, even if it does allow you to do deep validation into the memory block. In many cases, the validation function might even be an empty function if all the class holds is a couple of string variables. But it can be invaluable because it gives you some excellent debugging capabilities. One of the first things that I started using it
for was to provide a second level of data validation on a set of base classes that I had developed. While this should
not replace good old fashioned parameter and input
checking, it gave me another layer of assurance that everything was correct.
The neatest thing to use the validation function for is double-checking complex data structures after performing operations on them. For example, one time I had a relatively complex situation where two separate self-referential data structures both used the same allocated objects because of space considerations. After filling in the data structures with a large set of data, I used the validate function to look at the individual blocks from the heap and check that they were referentially correct. While I could have written a bunch of code to walk each data structure, any code that you write is an open target for bugs. By using the validate function, I could bounce through the allocated blocks using code that had already been tested, and got to check the data structures from different positions because it was in the order of allocation.
While it is a little more difficult to get C allocations set up, at least using the memory validation function is the same in both C and C++. All it takes is calling the VALIDATEALLBLOCKS macro. This expands in _DEBUG builds to a call to the ValidateAllBlocks routine. The parameter required is any value that you would like to get passed on through to the validation functions that you registered with the library. I have used this in the past to determine the depth of the validation that the function will perform. Keep in mind that this value is passed to every registered validation routine, so you might need to coordinate the values across your team.
To see the MemDumperValidator library in action, check out the Dump program shown in Figure 3. It is a stripped-down program that just shows you what you need to use the library. Now let’s look at how it is implemented. While I did not provide a code example, the MemDumperValidator library works quite well with MFC because MFC will call any previously registered client dump hook functions. This way you can get the best of both worlds!
The implementation for the MemDumperValidator library is contained in MemDumperValidator.cpp (see Figure 4). What makes the library work is the unique block value for each type of memory allocated. The debug runtime lets you assign unique values to the memory allocated by your program, which it refers to as client blocks. Since I wanted the library to be usable for C code as well as C++ code, the library keeps the values unique with the CheckMaxSubType function.
Figure 4: MemDumperValidator.cpp
/*----------------------------------------------------------------------
John Robbins
Microsoft Systems Journal
October 1997 - Bugslayer!
----------------------------------------------------------------------*/
#include "PCH.h"
#include "MemDumperValidator.h"
#include "CRTDBG_Internals.h"
// An internal typedef to just keep the source lines from being 600
// characters long.
typedef int (*PFNCOMPARE) ( const void * , const void * ) ;
////////////////////////////////////////////////////////////////////////
// File static variables.
////////////////////////////////////////////////////////////////////////
// The variable that is checked to see if life is initialized.
static BOOL g_bLibInit = FALSE ;
// The actual pointer to the array of client block dumpers and
// initializers. This is allocated out of a private heap.
static LPDVINFO g_pstDVInfo = NULL ;
// The number of items in the g_pstDVInfo array.
static DWORD g_dwDVCount = 0 ;
// The highest client subblock value used. This is so we can add items
// out of range of the highest seen so far.
static DWORD g_dwMaxSubtype = 0 ;
// The private heap that this library will allocate out all data from.
static HANDLE g_hHeap = NULL ;
// The critical section that everything will be protected with.
static CRITICAL_SECTION g_stCritSec ;
// The pointer to the previous dump client function. If this library
// does not have a dumper for the client block
static _CRT_DUMP_CLIENT g_pfnPrevDumpClient = NULL ;
////////////////////////////////////////////////////////////////////////
// Internal file prototypes.
////////////////////////////////////////////////////////////////////////
// The library initializer.
static BOOL InitializeLibrary ( void ) ;
// The library shutdown function.
static BOOL ShutdownLibrary ( void ) ;
// The dump function that the actual RTL will call.
static void DumpFunction ( void * pData , size_t nSize ) ;
// The function that the RTL will call when doing all client blocks.
static void DoForAllFunction ( void * pData , void * pContext ) ;
// Compares DVINFO structures for qsort and bsearch.
static int CompareDVInfos ( LPDVINFO pDV1 , LPDVINFO pDV2 ) ;
// Finds the registered dumper-validator block for a particular piece of
// memory.
static LPDVINFO FindRegisteredBlockType ( void * pData ) ;
// Keeps the maximum subtype straight.
static BOOL CheckMaxSubType ( ) ;
/*//////////////////////////////////////////////////////////////////////
The AutoMatic class.
This class is simply one that is used to create a static variable,
g_cBeforeAndAfter below. Since all constructors are called before
main/WinMain and destructors are called after main/WinMain, it gives me
a chance to set up and tear down the library.
Overall, there are some hacks in here you should be aware of. The
first is that the initialization segment for the whole file is declared
as compiler. This is not recommended at all. However, there is no
other way of ensuring static initialization order other than how things
are ordered on the command line. Since this can be rather difficult
when libraries like MFC use #pragma comment to automagically link
themselves in, this seems to be the only way.
The second big hack is that the destructor does the memory leak
checking and then shuts it off. This is because the RTL clears out the
dump client before calling _CrtDumpMemoryLeaks on shutdown. While I
suspect that the reasoning for clearing out the dump function is good,
it makes it rather difficult to use the nice little architecture I set
up here. My guess is that since the RTL is shutting down, and the
user's dump function could be using the RTL, it could lead to crashes.
I don't use any CRTL calls in here except to call the debug reporting
scheme. Since _CrtDumpMemoryLeaks uses these calls, they are safe to be
called.
Finally, in the constructor, I force all the debugging flags on
because what's the sense of having a debug build if you are not going to
take advantage of everything at your disposal?
//////////////////////////////////////////////////////////////////////*/
#pragma warning (disable : 4074)
#pragma init_seg(compiler)
class AutoMatic
{
public :
AutoMatic ( )
{
int iFlags = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ) ;
iFlags |= _CRTDBG_CHECK_ALWAYS_DF ;
iFlags |= _CRTDBG_DELAY_FREE_MEM_DF ;
iFlags |= _CRTDBG_LEAK_CHECK_DF ;
_CrtSetDbgFlag ( iFlags ) ;
}
~AutoMatic ( )
{
if ( TRUE == g_bLibInit )
{
EnterCriticalSection ( &g_stCritSec ) ;
// Do the leak checking, then turn off the option so the
// CRT does not show it.
_CrtDumpMemoryLeaks ( ) ;
int iFlags = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ) ;
iFlags &= ~_CRTDBG_LEAK_CHECK_DF ;
_CrtSetDbgFlag ( iFlags ) ;
ShutdownLibrary ( ) ;
}
}
} ;
// The static class.
static AutoMatic g_cBeforeAndAfter ;
////////////////////////////////////////////////////////////////////////
// Public Implementation starts here.
////////////////////////////////////////////////////////////////////////
int AddClientDV ( LPDVINFO lpDVInfo )
{
BOOL bRet = TRUE ;
__try
{
__try
{
ASSERT ( NULL != lpDVInfo ) ;
ASSERT ( FALSE ==
IsBadCodePtr ( (FARPROC)lpDVInfo->pfnDump) ) ;
ASSERT ( FALSE ==
IsBadCodePtr ( (FARPROC)lpDVInfo->pfnValidate ));
if ( ( NULL == lpDVInfo ) ||
( TRUE ==
IsBadCodePtr((FARPROC)lpDVInfo->pfnDump ) )||
( TRUE ==
IsBadCodePtr((FARPROC)lpDVInfo->pfnValidate ) ) )
{
_RPT0 ( _CRT_WARN , "Bad parameters to AddClientDV\n" );
return ( FALSE ) ;
}
// Has everything been initialized?
if ( FALSE == g_bLibInit )
{
InitializeLibrary ( ) ;
}
// Block access to the library.
EnterCriticalSection ( &g_stCritSec ) ;
// Do the trivial case add first.
if ( 0 == g_dwDVCount )
{
ASSERT ( NULL == g_pstDVInfo ) ;
g_dwDVCount = 1 ;
// Check if we are supposed to make up the new client
// value.
if ( 0 == CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) )
{
lpDVInfo->dwValue =
CLIENT_BLOCK_VALUE ( g_dwDVCount ) ;
g_dwMaxSubtype = 1 ;
}
else
{
g_dwMaxSubtype =
CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) ;
if ( FALSE == CheckMaxSubType ( ) )
{
g_dwDVCount = 0 ;
__leave ;
}
}
g_pstDVInfo =
(LPDVINFO)HeapAlloc ( g_hHeap ,
HEAP_GENERATE_EXCEPTIONS |
HEAP_ZERO_MEMORY ,
sizeof ( DVINFO ) );
g_pstDVInfo[ 0 ] = *lpDVInfo ;
__leave ;
}
// Is this a specific value add?
else if ( 0 != CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) )
{
LPDVINFO lpDV ;
// Make sure that this value is not already in the list.
lpDV = (LPDVINFO)bsearch ( lpDVInfo ,
g_pstDVInfo ,
g_dwDVCount ,
sizeof ( DVINFO ) ,
(PFNCOMPARE)CompareDVInfos );
ASSERT ( NULL == lpDV ) ;
if ( NULL != lpDV )
{
_RPT1 ( _CRT_WARN ,
"%08X is already in the MemDumperValidate "
"list!\n" ,
lpDVInfo->dwValue );
bRet = FALSE ;
__leave ;
}
// Check the block subtype against the highest we have
// seen so far. If this is the new high, save the
// value off.
if ( g_dwMaxSubtype <
CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) )
{
g_dwMaxSubtype =
CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) ;
if ( FALSE == CheckMaxSubType ( ) )
{
__leave ;
}
}
}
// Bump up the count.
g_dwDVCount++ ;
BOOL bTackOnEnd = FALSE ;
// If the user wants to just get a value assigned, then
// the returned number is one larger than the last one in
// the list.
if ( 0 == CLIENT_BLOCK_SUBTYPE ( lpDVInfo->dwValue ) )
{
// Bump up the max subtype that we have seen.
if ( FALSE == CheckMaxSubType ( ) )
{
g_dwDVCount-- ;
__leave ;
}
bTackOnEnd = TRUE ;
lpDVInfo->dwValue =
CLIENT_BLOCK_VALUE ( g_dwMaxSubtype ) ;
}
// Reallocate the array.
g_pstDVInfo =
(LPDVINFO)HeapReAlloc ( g_hHeap ,
HEAP_GENERATE_EXCEPTIONS |
HEAP_ZERO_MEMORY ,
g_pstDVInfo ,
g_dwDVCount * sizeof ( DVINFO));
if ( TRUE == bTackOnEnd )
{
g_pstDVInfo[ g_dwDVCount - 1 ] = *lpDVInfo ;
}
else
{
// Bummer, pound through the array and look for the slot
// to insert it.
DWORD iCurr = 0 ;
DWORD dwToFind =
CLIENT_BLOCK_SUBTYPE( lpDVInfo->dwValue );
while ( dwToFind >
CLIENT_BLOCK_SUBTYPE(g_pstDVInfo[iCurr].dwValue))
{
iCurr++ ;
if ( iCurr == g_dwDVCount )
{
// Tack it on the end.
g_pstDVInfo[ g_dwDVCount - 1 ] = *lpDVInfo ;
__leave ;
}
}
DWORD dwDest = (DWORD)g_pstDVInfo +
( (iCurr+1) * sizeof ( DVINFO ) );
DWORD dwSrc = (DWORD)g_pstDVInfo +
( iCurr * sizeof ( DVINFO ) ) ;
DWORD dwCount = ( ( g_dwDVCount - 1 ) - iCurr) *
sizeof ( DVINFO ) ;
// Move memory to allow the insertion.
memmove ( (void*)dwDest , (void*)dwSrc , dwCount ) ;
// Insert the element.
g_pstDVInfo[ iCurr ] = *lpDVInfo ;
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
ASSERT ( FALSE ) ;
bRet = FALSE ;
}
}
__finally
{
LeaveCriticalSection ( &g_stCritSec ) ;
}
return ( bRet ) ;
}
void ValidateAllBlocks ( void * pContext )
{
__try
{
// Block access to the library.
EnterCriticalSection ( &g_stCritSec ) ;
// The first thing to do is to let the CRT do its normal stuff.
_CrtCheckMemory ( ) ;
// Now, have the CRT call our library validator for all client
// blocks.
_CrtDoForAllClientObjects ( DoForAllFunction ,
pContext ) ;
}
__finally
{
LeaveCriticalSection ( &g_stCritSec ) ;
}
}
////////////////////////////////////////////////////////////////////////
// Private Implementation starts here.
////////////////////////////////////////////////////////////////////////
/*----------------------------------------------------------------------
FUNCTION : DumpFunction
DISCUSSION :
The function the the RTL will call for all client blocks. It
calculates which of the user's functions will be called.
PARAMETERS :
RETURNS :
----------------------------------------------------------------------*/
static void DumpFunction ( void * pData , size_t nSize )
{
__try
{
__try
{
// Block access to the library.
EnterCriticalSection ( &g_stCritSec ) ;
ASSERT ( NULL != pData ) ;
LPDVINFO lpDV = FindRegisteredBlockType ( pData ) ;
if ( ( NULL != lpDV ) && ( NULL != lpDV->pfnDump ) )
{
// Call the dumper registered for this block.
lpDV->pfnDump ( pData ) ;
}
// This is either a normal client block (not one that the
// user added to this list), or does not have a registered
// dumper so pass it on to the previous dumper function.
else if ( NULL != g_pfnPrevDumpClient )
{
g_pfnPrevDumpClient ( pData , nSize ) ;
}
else
{
// I just lifted the _printMemBlockData function out of
// DBGHEAP.C and put it here.
_CrtMemBlockHeader * pHead ;
pHead = pHdr ( pData ) ;
ASSERT ( NULL != pHead ) ;
#define MAXPRINT 16
int i ;
unsigned char ch ;
unsigned char printbuff[ MAXPRINT + 1 ] ;
unsigned char valbuff[ MAXPRINT * 3 + 1 ] ;
for ( i = 0 ;
i < min( (int)pHead->nDataSize , MAXPRINT ) ;
i++ )
{
ch = pbData(pHead)[ i ] ;
printbuff[ i ] = isprint( ch ) ? ch : ' ' ;
wsprintf ( (char*)&valbuff[ i * 3 ] , "%.2X " , ch);
}
printbuff[ i ] = '\0' ;
_RPT2 ( _CRT_WARN ,
" Data: <%s> %s\n" ,
printbuff ,
valbuff ) ;
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
ASSERT ( FALSE ) ;
_RPT0 ( _CRT_WARN , "There was a crash in DumpFunction!\n");
}
}
__finally
{
LeaveCriticalSection ( &g_stCritSec ) ;
}
}
/*----------------------------------------------------------------------
FUNCTION : DoForAllFunction
DISCUSSION :
The function that ValidateAllBlocks will call to have run over
all the client blocks. When called for a block, it will look at the
list of registered types and if there is a validator function, then
it will be called for the block.
PARAMETERS :
pData - The data to validate.
pContext - The context data originally passed to ValudateAllBlocks.
RETURNS :
None.
----------------------------------------------------------------------*/
static void DoForAllFunction ( void * pData , void * pContext )
{
__try
{
__try
{
// Block access to the library.
EnterCriticalSection ( &g_stCritSec ) ;
ASSERT ( NULL != pData ) ;
LPDVINFO lpDV = FindRegisteredBlockType ( pData ) ;
// Only call the validator if there is one. If there is not
// one, then there is nothing to do.
if ( ( NULL != lpDV ) && ( NULL != lpDV->pfnValidate ) )
{
// Call the validator registered for this block.
lpDV->pfnValidate ( pData , pContext ) ;
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
ASSERT ( FALSE ) ;
_RPT0 ( _CRT_WARN ,
"There was a crash in DoForAllFunction!\n" ) ;
}
}
__finally
{
LeaveCriticalSection ( &g_stCritSec ) ;
}
}
/*----------------------------------------------------------------------
FUNCTION : InitializeLibrary
DISCUSSION :
Completely initializes the library. All of the file static
variables are ready to be used.
PARAMETERS :
None.
RETURNS :
TRUE - The library was initialized.
FALSE - There was a problem.
----------------------------------------------------------------------*/
static BOOL InitializeLibrary ( void )
{
ASSERT ( FALSE == g_bLibInit ) ;
// Always start by initializing the critical section.
InitializeCriticalSection ( &g_stCritSec ) ;
// Create the private heap for this library.
g_hHeap = HeapCreate ( HEAP_GENERATE_EXCEPTIONS , 0 , 0 ) ;
// Now hook our dump function up.
g_pfnPrevDumpClient =
_CrtSetDumpClient ( (_CRT_DUMP_CLIENT)DumpFunction ) ;
g_bLibInit = TRUE ;
return ( TRUE ) ;
}
/*----------------------------------------------------------------------
FUNCTION : ShutdownLibrary
DISCUSSION :
Takes care of freeing all the memory and shutting down the library.
This MUST be called after the critical section has already been grabbed
because it will take care of releasing it and destroying it.
PARAMETERS :
None.
RETURNS :
TRUE - Everything was kosher.
FALSE - Danger, Will Robinson.
----------------------------------------------------------------------*/
static BOOL ShutdownLibrary ( void )
{
ASSERT ( TRUE == g_bLibInit ) ;
// Get rid of all the private memory in one fell swoop.
BOOL bRet = HeapDestroy ( g_hHeap ) ;
ASSERT ( TRUE == bRet ) ;
g_hHeap = NULL ;
// Set the previous dump client back.
if ( NULL != g_pfnPrevDumpClient )
{
_CrtSetDumpClient ( (_CRT_DUMP_CLIENT)g_pfnPrevDumpClient ) ;
}
// Reset all of the global variables.
g_pstDVInfo = NULL ;
g_dwDVCount = 0 ;
g_dwMaxSubtype = 0 ;
g_hHeap = NULL ;
g_pfnPrevDumpClient = NULL ;
g_bLibInit = FALSE ;
// Remember, the critical section is blocked here so clear it then
// get rid of it because we are done.
LeaveCriticalSection ( &g_stCritSec ) ;
DeleteCriticalSection ( &g_stCritSec ) ;
return ( TRUE ) ;
}
/*----------------------------------------------------------------------
FUNCTION : CompateDVInfos
DISCUSSION :
Compares the dwValue parameters for two DVINFO structures.
PARAMETERS :
pDV1, pDV2 - The structures to compare.
RETURNS :
< 0 - pDV1 < pDV2
0 - pDV1 = pDV2
> 0 - pDV1 > pDV2
----------------------------------------------------------------------*/
static int CompareDVInfos ( LPDVINFO pDV1 , LPDVINFO pDV2 )
{
ASSERT ( NULL != pDV1 ) ;
ASSERT ( NULL != pDV2 ) ;
if ( pDV1->dwValue < pDV2->dwValue )
{
return ( -1 ) ;
}
if ( pDV1->dwValue > pDV2->dwValue )
{
return ( 1 ) ;
}
return ( 0 ) ;
}
/*----------------------------------------------------------------------
FUNCTION : CheckMaxSubType
DISCUSSION :
A simple function that keeps the maximum subtype straight.
PARAMETERS :
None.
RETURNS :
TRUE - g_dwMaxSubtype is properly updated and ready for use.
FALSE - g_dwMaxSubtype is out of range.
----------------------------------------------------------------------*/
static BOOL CheckMaxSubType ( )
{
ASSERT ( (WORD)g_dwMaxSubtype < (WORD)0xFFFF ) ;
if ( g_dwMaxSubtype >= 0xFFFF )
{
_RPT0 ( _CRT_WARN ,
"Running max subtype value in "
"MemDumperValidator is too high!" ) ;
return ( FALSE ) ;
}
g_dwMaxSubtype++ ;
return ( TRUE ) ;
}
static LPDVINFO FindRegisteredBlockType ( void * pData )
{
// Get at the header for the block to get the client type.
_CrtMemBlockHeader * pHead ;
pHead = pHdr ( pData ) ;
ASSERT ( NULL != pHead ) ;
DVINFO stDVFind ;
LPDVINFO lpDV ;
stDVFind.dwValue = pHead->nBlockUse ;
// Try and find the value.
lpDV = (LPDVINFO)bsearch ( &stDVFind ,
g_pstDVInfo ,
g_dwDVCount ,
sizeof ( DVINFO ) ,
(PFNCOMPARE)CompareDVInfos ) ;
return ( lpDV ) ;
}
While allowing you to assign unique values to your allocated memory is a great feature of the debug runtime, there is only one small problem: the debug runtime does not have a documented way to get the block value from the hook functions. The hook functions are only passed a pointer to the user data, not the whole block the debug runtime allocates. Fortunately, with the source code to the runtime library I was able to see exactly how the debug runtime allocates memory blocks. They are all allocated as a _CrtMemBlockHeader structure defined in the DBGINT.H file.
Also in the file are macros to get at the _CrtMemBlockHeader from the user data pointer and to get the user data from a _CrtMemBlockHeader. I copied the _CrtMemBlockHeader structure and access macros into a header file, CRTDBG_Internals.h (see Figure 5), so I could get at the header information. While this is not a good practice, it works because the debug runtime _CrtMemBlockHeader structure did not change between Visual C++ 4.0 and Visual C++ 5.0. That doesn’t mean that it won’t change in a future version.
Figure 5: CRTDBG_Internals.h
/*----------------------------------------------------------------------
John Robbins
Microsoft Systems Journal, October 1997 - Bugslayer!
----------------------------------------------------------------------*/
#ifndef _CRTDBG_INTERNALS_H
#define _CRTDBG_INTERNALS_H
#define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext ;
struct _CrtMemBlockHeader * pBlockHeaderPrev ;
char * szFileName ;
int nLine ;
size_t nDataSize ;
int nBlockUse ;
long lRequest ;
unsigned char gap[nNoMansLandSize] ;
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;
#define pbData(pblock) ((unsigned char *) \
((_CrtMemBlockHeader *)pblock + 1))
#define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)
#endif // _CRTDBG_INTERNALS_H
One other nice thing about pulling the _CrtMemBlockHeader structure definition out is that you can use it to get more information from the _CrtMemState structures returned by _CrtMemCheckpoint because the first item in the structure is a pointer to a _CrtMemBlockHeader. Hopefully, a future version of the debug runtime will give us real access functions to get the memory block information.
With the gyrations needed to get the unique client block value figured out, the rest of the library is relatively simple. The unique client block value for the block and the dumper and validator functions are stored in a DVINFO structure. The global data structure in MemDumperValidator.cpp, g_pstDVInfo, is an array of DVINFO structures in block value order. Storing the values in this array means that you just have to use bsearch to find the functions that know how to process the memory block.
The memory for the g_stDVInfo array is allocated out of a private heap that I set up just for the library. As discussed above, the MemDumperValidator library uses the #pragma init_seg directive to get the C++ static class, AutoMatic, constructed before your code and destructed after your code. The constructor for the AutoMatic class just calls the InitializeLibrary function to get the file static variables set up and the private heap allocated.
Now that all the framework stuff is out of the way, I can turn to the code that uses the debug runtime. The last step in the InitializeLibrary function is a call to _CrtSetDumpClient, which sets DumpFunction (a very original name) as the client dump function. When DumpFunction is called by the library, it uses the FindRegisteredBlockType helper function to see if the block has a registered dump function. If there is one for the block, it is called to dump the block. If there is not a registered dump function, and if there is a previous client dump function, then that client dump function is called. Finally, if there was no registered dump function and no previous client dump function, then I just dump the block the same way the debug runtime does it.
The ValidateAllBlocks function uses two features of the debug runtime. First it calls _CrtCheckMemory to allow it to check for overwrites and underwrites. It then calls _CrtDoForAllClientObjects, which will loop through all the allocated client blocks and call the callback function passed as a parameter. My DoForAllFunction does much the same thing as DumpFunction in looking to see if the block type is registered; if so, it calls the validation function for the block.
When I first got the MemDumperValidator library running, I was pretty happy how it all worked—except that, when the program terminated, I never saw the nicely formatted output from my dumping functions if I had memory leaks. The memory dumps were just the old standard debug runtime-style dumps. I tracked this down and was surprised to see that the runtime termination routines call _CrtSetDumpClient with a parameter of NULL, thus clearing out my dump hook before calling _CrtDumpMemoryLeaks. This was a little distressing, until it dawned on me that I just had to do the final memory leak checking myself. Fortunately, I had the perfect place to do it.
Since I was already using the #pragma init_seg(compiler) to get the AutoMatic class initialized before your code and to call the destructor after your code, I just needed to do the leak checking there, then turn off the _CRTDBG_LEAK_CHECK_DF flag so that the debug runtime does not do it. The only caveat is that you need to make sure that the runtime library of your choice comes before MemDumperValidator.lib if you link with the NODEFAULTLIB switch. This is needed so that you get everything created and destroyed in the correct order.
If you think about it, it makes sense that the runtime clears out any dump hook installed. If your dump hook were using any runtime routines, such as printf, it could crash the termination of your program because the runtime is in the middle of shutting down when it calls _CrtDumpMemoryLeaks. If you follow the rules and always link with the runtime library before any others, you will be fine because the MemDumperValidator library is shut down before the runtime. To avoid problems, only use the _RPTn macros in your dumper routines anyway, since this is all that _CrtDumpMemoryLeaks uses.
Now that you can dump and validate all of your memory blocks in a clean and usable fashion, it is time to put some stress in your life. Back in the old 16-bit Windows days, the neatest program that came with the SDK was STRESS.EXE. It allowed you to do all sorts of nasty things like eat up disk space, gobble up the GDI heap, and use up the file handles. It even had a neat icon of an elephant walking a tightrope.
While it is much harder to stress Win32 programs, the debug runtime allows you to hook into the allocation system and let it succeed or fail. While I will leave it up to you to write the disk-eating code, MemStressLib will give you a means to stress your C and C++ memory allocation (see Figure 6). To make it really easy to use, I even wrote a Visual Basic front end, MemStress, so you can specify exactly what conditions you would like to fail.
Figure 6: MemStressLib.cpp
/*----------------------------------------------------------------------
John Robbins
Microsoft Systems Journal, October 1997 - Bugslayer!
----------------------------------------------------------------------*/
// Get everything included.
#include "PCH.h"
#include "MemStressLib.h"
#include "MemStressLibConstants.h"
#include "CRTDBG_Internals.h"
////////////////////////////////////////////////////////////////////////
// This library needs USER32.DLL for MessageBox so force the link
// against it in case the user forgets.
////////////////////////////////////////////////////////////////////////
#pragma comment ( lib , "user32.lib" )
////////////////////////////////////////////////////////////////////////
// Internal Data Structures
////////////////////////////////////////////////////////////////////////
// The struct that holds a single file marked for failure and the line
// in the file where allocations are supposed to fail. If the line is -1,
// then any allocations from the file are supposed to fail.
// BIG NOTE: Notice that even in UNICODE builds, this structure only has
// character buffers. No matter what, __FILE__ is always defined in terms of
// chars. So everything is converted when it is read in.
typedef struct tag_FAILFILE
{
char szFile[ MAX_PATH + 1 ] ;
long lLine ;
} FAILFILE , * LPFAILFILE ;
// The struct that holds all of the options as they come out of the INI
// file.
typedef struct tag_FAILUREINFO
{
// The CRT options that should be set for the user.
BOOL bCRTCheckMemory ;
BOOL bCRTDelayMemFrees ;
BOOL bCRTLeakChecking ;
// The general options for the hook. If any of the UINT types are
// zero, then they are not active.
BOOL bGENFailAllAllocs ;
UINT uiGENFailEveryN ;
UINT uiGENFailAfterX ;
UINT uiGENFailOverY ;
BOOL bGENAskOnEach ;
// The total file count.
UINT uiFileCount ;
// The array of files to fail.
LPFAILFILE pFailFiles ;
} FAILUREINFO , * LPFAILUREINFO ;
////////////////////////////////////////////////////////////////////////
// Unfortunately, the CRT hooks do not get told if the memory being
// reallocated is being done in place or will cause a free first.
// Thus, it is impossible to know for sure exactly how much memory is
// outstanding in the system. The statistics keeps are only for calls
// to allocation functions (new and malloc) and deallocation functions
// (delete and free).
// Memory statistics are recorded before any processing for failures
// takes place.
typedef struct tag_MEMSTATS
{
// The total highwater mark of allocations so far.
long lMaxAllocated ;
// The total number of calls to allocation functions.
long lTotalAllocCalls ;
// The total number of calls to realloc functions.
long lTotalReAllocCalls ;
// The total number of calls to release functions.
long lTotalReleaseCalls ;
// The total amount of memory that is currently in use.
long lCurrentlyInUse ;
} MEMSTATS , * LPMEMSTATS ;
////////////////////////////////////////////////////////////////////////
// Static Variable Declarations
////////////////////////////////////////////////////////////////////////
// The critical section that everything will be protected with.
static CRITICAL_SECTION g_stCritSec ;
// The one global flag that indicates that the library was properly
// initialized and ready for use.
static BOOL g_bLibIsInit = FALSE ;
// The private heap that will hold all of the allocations for this
// module. Keep in mind that this library cannot do any normal CRT
// memory allocations because it could get into nasty recursive
// situations.
static HANDLE g_hHeap = NULL ;
// The failure information for this run of the library.
static LPFAILUREINFO g_pFailInfo = NULL ;
// The internal statistics.
static MEMSTATS g_stMemStats ;
// The previously installed hook.
static _CRT_ALLOC_HOOK pfnPrevHook ;
////////////////////////////////////////////////////////////////////////
// Internal Function Declarations
////////////////////////////////////////////////////////////////////////
// The function that actually does the INI reading.
static LPFAILUREINFO ProcessINIFileA ( LPCSTR szProgram ) ;
// The actual allocation hook.
static int AllocationHook ( int nAllocType ,
void * pvData ,
size_t nSize ,
int nBlockUse ,
long lRequest ,
const unsigned char * szFileName ,
int nLine ) ;
////////////////////////////////////////////////////////////////////////
// Function Implementation Starts Here
////////////////////////////////////////////////////////////////////////
// The initialization function.
int InitializeMemStressA ( const char * szProgName )
{
// The return value.
BOOL bRet = TRUE ;
__try
{
__try
{
// Check all of the basic assumptions.
ASSERT ( FALSE == g_bLibIsInit ) ;
ASSERT ( NULL == g_hHeap ) ;
ASSERT ( NULL == g_pFailInfo ) ;
ASSERT ( NULL != szProgName ) ;
// Initialize the few items that all routines in here will
// have to share.
InitializeCriticalSection ( &g_stCritSec ) ;
// Now immediately, try and grab it.
EnterCriticalSection ( &g_stCritSec ) ;
memset ( &g_stMemStats , NULL , sizeof ( MEMSTATS ) ) ;
// Create the private heap for this library.
g_hHeap = HeapCreate ( HEAP_GENERATE_EXCEPTIONS , 0 , 0 ) ;
// Get the failure information for the file.
g_pFailInfo = ProcessINIFileA ( szProgName ) ;
// Set the main CRT flags.
int iFlags = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ) ;
if ( TRUE == g_pFailInfo->bCRTCheckMemory )
{
iFlags |= _CRTDBG_CHECK_ALWAYS_DF ;
}
if ( TRUE == g_pFailInfo->bCRTDelayMemFrees )
{
iFlags |= _CRTDBG_DELAY_FREE_MEM_DF ;
}
if ( TRUE == g_pFailInfo->bCRTLeakChecking )
{
iFlags |= _CRTDBG_LEAK_CHECK_DF ;
}
_CrtSetDbgFlag ( iFlags ) ;
// Set the allocation hook.
pfnPrevHook =
_CrtSetAllocHook ( (_CRT_ALLOC_HOOK)AllocationHook ) ;
g_bLibIsInit = TRUE ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
ASSERT ( FALSE ) ;
// The library was unable to initialize.
g_bLibIsInit = FALSE ;
bRet = FALSE ;
}
}
__finally
{
// Make sure that we always execute this!
LeaveCriticalSection ( &g_stCritSec ) ;
}
return ( bRet ) ;
}
int InitializeMemStressW ( const wchar_t * szProgName )
{
ASSERT ( NULL != szProgName ) ;
char szBuff[ MAX_PATH + 1 ] ;
int iRet ;
iRet = WideCharToMultiByte ( CP_ACP ,
0 ,
szProgName ,
-1 ,
szBuff ,
sizeof ( szBuff ) ,
NULL ,
NULL ) ;
ASSERT ( 0 != iRet ) ;
return ( InitializeMemStressA ( szBuff ) ) ;
}
int TerminateMemStress ( void )
{
// The return value.
BOOL bRet = TRUE ;
__try
{
__try
{
ASSERT ( TRUE == g_bLibIsInit ) ;
ASSERT ( NULL != g_pFailInfo ) ;
// Now immediately, try and grab it.
EnterCriticalSection ( &g_stCritSec ) ;
// Notice that I do not explicitly free any of the memory
// allocated out of the private heap. Since the whole
// block gets destroyed in a single call, there is no
// reason to do each subblock.
BOOL bHRet = HeapDestroy ( g_hHeap ) ;
ASSERT ( FALSE != bHRet ) ;
g_hHeap = NULL ;
g_pFailInfo = NULL ;
g_bLibIsInit = FALSE ;
// Set the allocation hook back to what it was.
_CrtSetAllocHook ( (_CRT_ALLOC_HOOK)pfnPrevHook ) ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
ASSERT ( FALSE ) ;
bRet = FALSE ;
}
}
__finally
{
LeaveCriticalSection ( &g_stCritSec ) ;
// Just get rid of the critical section.
DeleteCriticalSection ( &g_stCritSec ) ;
}
return ( bRet ) ;
}
/*----------------------------------------------------------------------
Function : AllocationHook
Discussion :
The memory allocation hook that is installed to handle the memory
failures.
Parameters :
nAllocType - The type of allocation operation, _HOOK_ALLOC,
_HOOK_REALLOC, or _HOOK_FREE.
pvData - The pointer to the data if nAllocType is _HOOK_FREE,
otherwise NULL.
nSize - The size of the memory block.
nBlockUse - The type of memory block, _CRT_BLOCK, etc.
lRequest - The block request number.
szFileName - The file that had the memory operation.
nLine - The line where the memory operation took place.
Returns :
TRUE - The allocation function should succeed.
FALSE - The allocation function should fail.
----------------------------------------------------------------------*/
static int AllocationHook ( int nAllocType ,
void * pvData ,
size_t nSize ,
int nBlockUse ,
long /*lRequest*/ ,
const unsigned char * szFileName ,
int nLine )
{
BOOL bRet = TRUE ;
__try
{
__try
{
// Grab the critical section.
EnterCriticalSection ( &g_stCritSec ) ;
// The CRT blocks must be ignored or we will cause many
// problems.
if ( _CRT_BLOCK == nBlockUse )
{
__leave ;
}
// Skip all the processing for _HOOK_FREE type calls.
if ( _HOOK_FREE == nAllocType )
{
_leave ;
}
// Now go through the litany of possible failure cases.
if ( TRUE == g_pFailInfo->bGENFailAllAllocs )
{
bRet = FALSE ;
__leave ;
}
if ( TRUE == g_pFailInfo->bGENAskOnEach )
{
char szBuff [ 1024 ] ;
wsprintfA ( szBuff ,
k_MSGFMT ,
szFileName ,
nLine ) ;
if ( IDYES == MessageBoxA ( GetActiveWindow ( ) ,
szBuff ,
k_MSGTITLE ,
MB_YESNO ) )
{
bRet = FALSE ;
__leave ;
}
}
if ( 0 != g_pFailInfo->uiGENFailEveryN )
{
if ( 0 == g_stMemStats.lTotalAllocCalls %
g_pFailInfo->uiGENFailEveryN )
{
bRet = FALSE ;
__leave ;
}
}
if ( 0 != g_pFailInfo->uiGENFailAfterX )
{
if ( g_pFailInfo->uiGENFailAfterX <=
(UINT)g_stMemStats.lMaxAllocated )
{
bRet = FALSE ;
_leave ;
}
}
if ( 0 != g_pFailInfo->uiGENFailOverY )
{
if ( nSize > g_pFailInfo->uiGENFailOverY )
{
bRet = FALSE ;
_leave ;
}
}
if ( 0 != g_pFailInfo->uiFileCount )
{
// Because the value for the __FILE__ macro seems to be
// randomly generated, I need to strip off any path
// stuff that may, or may not, be there.
char szJustFilename[ MAX_PATH ] ;
char szExt[ MAX_PATH ] ;
_splitpath ( (LPCSTR)szFileName ,
NULL ,
NULL ,
szJustFilename ,
szExt ) ;
strcat ( szJustFilename , szExt ) ;
for ( UINT i = 0 ;
i < g_pFailInfo->uiFileCount ;
i++ )
{
if ( 0 ==
stricmp ( g_pFailInfo->pFailFiles[i].szFile ,
(LPCSTR)szJustFilename ) )
{
if (( -1 == g_pFailInfo->pFailFiles[i].lLine )||
( nLine ==
g_pFailInfo->pFailFiles[i].lLine ))
{
bRet = FALSE ;
__leave ;
}
}
}
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
}
__finally
{
// Now that the allocation success or failure is known, do the
// statistics upkeep.
if ( _HOOK_FREE == nAllocType )
{
// Since the size is always zero when this is
// called for a free function, get the
// information ourselves.
_CrtMemBlockHeader * pHead = pHdr(pvData) ;
g_stMemStats.lTotalReleaseCalls++ ;
g_stMemStats.lCurrentlyInUse -= pHead->nDataSize ;
// Since the user can easily start and stop the hook
// whenever they want to, we might see extra calls to frees
// here. If that is the case, don't let the currently
// used go to less than zero.
if ( g_stMemStats.lCurrentlyInUse <= 0 )
{
g_stMemStats.lCurrentlyInUse = 0 ;
}
}
else if ( TRUE == bRet )
{
g_stMemStats.lMaxAllocated += nSize ;
g_stMemStats.lCurrentlyInUse += nSize ;
switch ( nAllocType )
{
case _HOOK_ALLOC :
g_stMemStats.lTotalAllocCalls++ ;
break ;
case _HOOK_REALLOC :
g_stMemStats.lTotalReAllocCalls++ ;
break ;
default :
ASSERT ( FALSE ) ;
break ;
}
}
// Make sure that we always execute this!
LeaveCriticalSection ( &g_stCritSec ) ;
}
return ( bRet ) ;
}
// The function that does the INI reading.
static LPFAILUREINFO ProcessINIFileA ( LPCSTR szProgram )
{
// Allocate the space for the buffer that will be returned.
LPFAILUREINFO pFailInfo =
(LPFAILUREINFO)HeapAlloc ( g_hHeap ,
HEAP_GENERATE_EXCEPTIONS |
HEAP_ZERO_MEMORY ,
sizeof ( FAILUREINFO ) );
// Look to see if the section with the program name really exists.
// If it does not the default section will be used.
char szBuff [ MAX_PATH + 1 ] ;
DWORD dwRet ;
LPCSTR lpAppName ;
dwRet = GetPrivateProfileSectionA ( szProgram ,
szBuff ,
sizeof ( szBuff ) ,
k_INI_FILENAME ) ;
if ( 0 == dwRet )
{
// The section for this program does not exist so use the
// default.
lpAppName = k_INI_DEFAULTSECTION ;
}
else
{
lpAppName = szProgram ;
}
// Pound through and get all of the fields.
pFailInfo->bCRTCheckMemory =
GetPrivateProfileIntA ( lpAppName ,
k_INI_CRTCHECKMEM ,
1 ,
k_INI_FILENAME );
pFailInfo->bCRTDelayMemFrees =
GetPrivateProfileIntA ( lpAppName ,
k_INI_CRTDELAYMEM ,
1 ,
k_INI_FILENAME );
pFailInfo->bCRTLeakChecking =
GetPrivateProfileIntA ( lpAppName ,
k_INI_CRTCHECKLEAKS ,
1 ,
k_INI_FILENAME );
pFailInfo->bGENFailAllAllocs =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENFAILALLALLOCS,
0 ,
k_INI_FILENAME );
pFailInfo->uiGENFailEveryN =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENFAILEVERYNALLOCS ,
0 ,
k_INI_FILENAME );
pFailInfo->uiGENFailAfterX =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENFAILAFTERXBYTES ,
0 ,
k_INI_FILENAME );
pFailInfo->uiGENFailOverY =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENFAILOVERYBYTES ,
0 ,
k_INI_FILENAME );
pFailInfo->bGENAskOnEach =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENASKONEACHALLOC ,
0 ,
k_INI_FILENAME );
// Now for the fun stuff, loop through and load the files to fail.
pFailInfo->uiFileCount =
GetPrivateProfileIntA ( lpAppName ,
k_INI_GENNUMFILEFAILURES ,
0 ,
k_INI_FILENAME );
if ( 0 != pFailInfo->uiFileCount )
{
// Allocate the space in the structure.
pFailInfo->pFailFiles =
(LPFAILFILE)HeapAlloc ( g_hHeap ,
HEAP_GENERATE_EXCEPTIONS |
HEAP_ZERO_MEMORY ,
sizeof ( FAILFILE ) *
pFailInfo->uiFileCount );
char szCurrFile [ 30 ] ;
LPSTR pEndOfConst ;
UINT uiCurrIndex ;
UINT uiCount ;
LPSTR pTok1 ;
LPSTR pTok2 ;
long lData ;
strcpy ( szCurrFile , k_INI_GENFILEFAILPREFIX ) ;
pEndOfConst = szCurrFile + strlen ( k_INI_GENFILEFAILPREFIX ) ;
uiCurrIndex = 0 ;
for ( uiCount = 1 ;
uiCount <= pFailInfo->uiFileCount ;
uiCount++ )
{
// Build up the key to read.
itoa ( uiCount , pEndOfConst , 10 ) ;
// Read the key.
dwRet = GetPrivateProfileStringA ( lpAppName ,
szCurrFile ,
"" ,
szBuff ,
sizeof ( szBuff ) ,
k_INI_FILENAME ) ;
// If nothing was read, then just go for the next one.
if ( 0 == dwRet )
{
continue ;
}
// Extract the two parts of the string.
pTok1 = strtok ( szBuff , k_SEP_FILEANDLINE ) ;
pTok2 = strtok ( NULL , k_SEP_FILEANDLINE ) ;
if ( ( NULL == pTok1 ) || ( NULL == pTok2 ) )
{
continue ;
}
// Convert the second string into a number.
lData = atol ( pTok2 ) ;
if ( 0 == lData )
{
continue ;
}
// Whew! We can go ahead and add this one.
pFailInfo->pFailFiles[ uiCurrIndex ].lLine = lData ;
strcpy ( pFailInfo->pFailFiles[uiCurrIndex].szFile ,
pTok1 ) ;
// Update the array counter.
uiCurrIndex++ ;
}
// Set the count to the proper count since some entries might
// have been thrown away.
pFailInfo->uiFileCount = uiCurrIndex ;
}
return ( pFailInfo ) ;
}
The memory stress library lets you force allocation failures based on various criteria: all allocations, on every n allocations, after x bytes are allocated, on requests over y bytes, on all allocations out of a file, and on a specific line in a file. In addition, you can have MemStressLib prompt you on each allocation, and you can also set the debug runtime flags you would like in effect for your program. The MemStressDemo program is a sample MFC program that allows you to experiment with setting different options from the MemStress UI and seeing the results.
Using MemStressLib is relatively simple. In your code, include MemStressLib.h and call the INITMEMSTRESS macro with the name of your program. To stop the memory allocation hooking, use the SHUTDOWNMEMSTRESS macro. You can start and stop the hook as many times as you like when running your program.
When you have compiled your program, start the MemStress user interface, click the Add Program button, and type the same name that you specified in the INITMEMSTRESS macro. After you have selected the failure options that you want, press the Save Settings For This Program button to save the settings into MEMSTRESS.INI. Now you can run your program and see how it behaves when it fails memory allocations.
You will probably want to be very selective about using MemStressLib. If, for example, you specify that you want all allocations over 100 bytes to fail, and you have the INITMEMSTRESS call in your MFC application’s InitInstance function, you will probably take down MFC because it will be unable to initialize. It is best to limit the use of MemStressLib to key areas in your code so you can test them in isolation.
The vast majority of the implementation of MemStressLib is in the reading and processing of the MEMSTRESS.INI file, where all the settings for individual programs are stored. From the debug runtime perspective, the important function is the call to _CrtSetAllocHook during the MemStressLib initialization as it gets the hook function, AllocationHook, set as the allocation hook. If the allocation hook function returns TRUE, then the allocation request is allowed to continue. By returning FALSE, the allocation hook can have the debug runtime fail the allocation request. The allocation hook only has one hard requirement from the debug runtime: if the type of block, as specified by the nBlockUse parameter, is marked as a _CRT_BLOCK, the hook function must return TRUE to allow the allocation to take place.
The allocation hook gets called on every type of allocation function. The different types, as specified in the first parameter to the hook, are: _HOOK_ALLOC, _HOOK_REALLOC, or _HOOK_FREE. In my AllocationHook function, if the type is _HOOK_FREE I skip all the code that determines if the memory request should pass or fail. For _HOOK_ALLOC and _HOOK_REALLOC types, my AllocationHook function does a series of if statements to determine whether any of the failure conditions are met. If one is met, I set up to return FALSE.
In the AllocationHook __finally block, I track all the statistics for the allocations. The only interesting part is when processing a _HOOK_FREE type allocation, I need to do undocumented work to get at the _CrtMemBlockHeader structure. The debug runtime always calls the hook function for _HOOK_FREE types with a size value of zero, which makes it a little difficult to check for high-water-mark failures. Also, since the actual allocation hook can come and go during a run of the program, I have to make sure that I do not accidentally get into negative amounts of allocated memory when processing the _HOOK_FREE type.
When I looked back on the code for this column, I was struck by how little of it really dealt with the debug runtime and how much of it did the setup work for MemDumperValidator and MemStressLib. I hope you find the libraries useful and they help you find bugs! I encourage you to look for other libraries that you could develop with the debug runtime.
In my future column, I want to offer some concise tips that you can use to fight bugs in your code or speed up
your debugging. If you have any tips, I would love to hear about them. I will be glad to acknowledge your contribution; just think, you will be able to impress your friends by searching for your name on the MSDN CD! Now, on to my inaugural tips:
Tip 1: Get rid of the MSDEV splash screen when using Just In Time debugging. First, run REGEDIT and open the key HKEY_LOCAL_MACHINE\Software\Microsoft\
Windows NT\CurrentVersion\AeDebug. Then, change the Debugger key from “<your path>\msdev.exe -p %ld -e %ld” to “<your path>\msdev.exe -nologo -p %ld -e %ld.”
Tip 2: Always check CloseHandle’s return value. Even though there is not much you can do when CloseHandle returns FALSE, it is generally indicative of a serious problem. Since Win32 will reuse handle values, it is quite easy to accidentally close an open handle being used elsewhere in your code, causing weird random bugs. When you call CloseHandle on a handle that has already been closed, it can sometimes cause random crashes that are impossible to track down. Place all your calls to CloseHandle in VERIFY macros so that you stand a fighting chance of finding those bugs in _DEBUG builds. (Thanks to Jim Austin