|
Implementing the MemDumperValidator functions was generally straightforward. The first unexpected problem I had to deal with was that the DCRT library doesn't document a way for the hook functions to get the memory block value. The hook functions are passed only a pointer to the user data, not the whole memory block the DCRT library allocates. Fortunately, with the DCRT library source code, I was able to see exactly how the library allocates memory blocks. Memory blocks are all allocated as a _CrtMemBlockHeader structure defined in the DBGINT.H file.
Also in the DBGINT.H file are macros to get at the _CrtMemBlockHeader from a user data pointer and to get at the user data from a _CrtMemBlockHeader pointer. I copied the _CrtMemBlockHeader structure and access macros into a header file, CRTDBG_INTERNALS.H (shown in Listing 17-4), so that I could get at the header information. Although relying on a copy of a structure definition when the definition might change isn't a good practice, it works in this case because the DCRT library _CrtMemBlockHeader structure hasn't changed since Visual C++ 4. That doesn't mean that this structure won't change in a future version of Visual C++. If you're using the MemDumperValidator extension, you need to quickly check each service pack and major release of the compiler to see whether the internal structures have changed.
Listing 17-4: CRTDBG_INTERNALS.H
/*---------------------------------------------------------------------- Debugging Applications for Microsoft .NET and Microsoft Windows Copyright (c) 1997-2003 John Robbins -- All rights reserved. ----------------------------------------------------------------------*/ #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
If you'd feel more comfortable with using DBGINT.H directly, you can replace the structure definition in CRTDBG_INTERNALS.H with #include DBGINT.H. You'll also need to add "$(VCInstallDir)VC7\CRT\SRC" to both your master INCLUDE environment variable and the Include files directories in the Options dialog box, Projects folder, VC++ Directories property page. Because not everyone installs the CRT library source code, though they should, I decided it would be easier if I just included the structure definition directly.
You can also use the _CrtMemBlockHeader structure definition to get more information from the _CrtMemState structures returned by _CrtMemCheckpoint because the first item in the structure is a pointer to a _CrtMemBlockHeader. I hope a future version of the DCRT library gives us real access functions to get the memory block information.
As you look through the source code in MEMDUMPERVALIDATOR.CPP, part of the BUGSLAYERUTIL.DLL project included with this book's sample files, you might notice that I use straight Windows application programming interface (API) heap functions from the HeapCreate family for internal memory management. The reason I use these API functions is that the dump and hook functions you use with the DCRT library will cause reentrancy if you use routines from the standard library. Keep in mind that I'm not talking about multithreaded reentrancy. If my hook code allocates memory with a call to malloc, my hook would be reentered because the hooks are called on every memory allocation.
After I finished implementing MemDumperValidator and started to test it, I was pleased that the extension worked as planned. However, as I was pondering all the ways that a program can allocate heap memory, I started to break out in a cold sweat. Were static constructors that possibly allocate memory going to give me any problems? As I looked at my initial code for MemDumperValidator, I discovered a glaring hole in my logic.
Although most developers don't do it much, in some cases, memory is allocated before an application's entry point. The problem with my approach in MemDumperValidator was that I needed to ensure that the appropriate flags to _CrtSetDbgFlag are set before any allocations take place.
The last thing I wanted to do with MemDumperValidator was force you to remember to call some goofy initialization function before you could use the library. It's bad enough that you have to drag around your BSMDVINFO structures for C programming. I wanted to make MemDumperValidator as automatic as possible so that more developers would use it—without any hassle.
Fortunately, my cold sweat didn't last too long because I remembered the #pragma init_seg directive, which can be used to control the initialization and destruction order of statically declared values. You can pass one of several options to the #pragma init_seg directive: compiler, lib, user, section name, and funcname. The first three are the important ones.
The compiler option is reserved for the Microsoft compiler, and any objects specified for this group are constructed first and destructed last. Those marked as lib are constructed next and destructed before the compiler-marked group, and those marked user are constructed last and terminated first.
Because the code in MemDumperValidator needs to be initialized before your code, I could just specify lib as the directive to #pragma init_seg and be done with it. However, if you're creating libraries and marking them as lib segments (as you should) and want to use my code, you still need to initialize my code before your code. To handle this contingency, I set the #pragma init_seg directive as compiler. Although you should always follow the rules when it comes to proper segment initializations, using the compiler option with debug code is safe enough.
Because the initialization idea works only with C++ code, MemDumperValidator uses a special static class, named AutoMatic, that simply calls the _CrtSetDbgFlag function. I need to go to all this trouble because it's the only way to set the DCRT flags before any other library's initialization. Additionally, as you'll see in a moment, to get around some limitations in the DCRT library memory leak checking, I need to do some special processing on the class destruction. Even though the MemDumperValidator has only a C interface, I can take advantage of C++ to get the extension up and running so that it's ready when you call it.
I survived my bout of anxiety over initialization issues and finally got the MemDumperValidator extension running. I was happy with 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 DCRT library dumps. I tracked down the "missing" leak reports and was surprised to see that the DCRT library termination functions call _CrtSetDumpClient with a parameter of NULL, thus clearing out my dump hook before calling _CrtDumpMemoryLeaks. I was distressed by this behavior 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.
Because I was already using the #pragma init_seg(compiler) directive 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 and then turn off the _CRTDBG_LEAK_CHECK_DF flag so that the DCRT library didn't do its own reporting. The only caveat with using this approach is that you need to make sure that the CRT library of your choice comes before BUGSLAYERUTIL.LIB if you link with the /NODEFAULTLIB switch. When you link against BUGSLAYERUTIL.LIB, the CRT libraries can't depend on their #pragma init_seg(compiler) directive to ensure that their data gets initialized first and destroyed last, so you need to enforce the correct ordering yourself.
If you think about it, having the DCRT library clear out any dump hooks installed makes sense. If your dump hook were using any CRT library functions, such as printf, it could crash the termination of your program because the library is in the middle of shutting down when _CrtDumpMemoryLeaks is called. If you follow the rules and always link with the DCRT library before any other libraries, you'll be fine because the MemDumperValidator functions are shut down before the DCRT library shuts down. To avoid problems, use the _RPTn and _RPTFn macros only in your dumper functions anyway, because _CrtDumpMemoryLeaks uses only these macros.
|