How to prevent a memory tool from monitoring your C/C++ allocations
A little known fact is that the Microsoft C Runtime (CRT) has a feature which allows some allocations (in the debug runtime) to be tagged with flags that causes these allocations to be ignored by the built in memory tracing routines. A good memory allocation tool will also use these flags to determine when to ignore memory allocations – thus not reporting any allocations that Microsoft think should remain hidden.
A customer problem
The inspiration for this article was a customer reporting that Memory Validator was not reporting any allocations in a particular DLL of his mixed mode .Net/native application. The application was interesting in that it was a combination of C#, C++ written with one version of Visual Studio and some other DLLs also written in C++ with another version of Visual Studio. Only the memory for one of the DLLs was not being reported by Memory Validator and the customer wanted to know why and could we please fix the problem?
After some investigation we found the problem was a not with Memory Validator but with the DLL in question making a call to _CrtSetDbgFlag(0); which turned off all memory tracking for that DLL. Memory Validator honours the memory tracking flags built into Visual Studio and thus did not report these memory allocations. Armed with this information the customer did some digging into their code base and found that someone had deliberately added this call into their code. Removing the call fixed the problem.
The rest of this article explains how Microsoft tags data to be ignored and what flags are used to control this process.
Why does Microsoft mark these allocation as ignore?
The reason for this is that these allocations are for internal housekeeping and sometimes also for one-off allocations that will exist until the end of the application lifetime. Such allocations could show up as memory leaks at the end of the application – that would be misleading as they were intended to persist. Better to mark them as “ignore” and not report them during a memory leak report.
Microsoft debug CRT header block
Microsoft’s debug CRT prefixes each allocation with a header block. That header block looks like this:
#define nNoMansLandSize 4 typedef struct _CrtMemBlockHeader { struct _CrtMemBlockHeader * pBlockHeaderNext; struct _CrtMemBlockHeader * pBlockHeaderPrev; char * szFileName; int nLine; #ifdef _WIN64 /* These items are reversed on Win64 to eliminate gaps in the struct * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is * maintained in the debug heap. */ int nBlockUse; size_t nDataSize; #else /* _WIN64 */ size_t nDataSize; int nBlockUse; #endif /* _WIN64 */ long lRequest; unsigned char gap[nNoMansLandSize]; /* followed by: * unsigned char data[nDataSize]; * unsigned char anotherGap[nNoMansLandSize]; */ } _CrtMemBlockHeader;
How does Microsoft tag an allocation as ignore?
When the CRT wishes an allocation to be ignored for memory tracking purposes, six values in the debug memory allocation header for each allocation are set to specific values.
Member | Value | #define |
---|---|---|
nLine | 0xFEDCBABC | IGNORE_LINE |
nBlockUse | 0x3 | IGNORE_BLOCK |
lRequest | 0x0 | IGNORE_REQ |
szFileName | NULL | |
pBlockHeaderNext | NULL | |
pBlockHeaderPrev | NULL |
The Microsoft code goes out of its way to ensure no useful information can be gained from the header block for these ignored items.
When we first created MV we noticed that items marked as ignored should be ignored, otherwise you can end up with FALSE positive noise reported at the end of a memory debugging session due to the internal housekeeping of MFC/CRT.
How can you use this information in your application?
Microsoft also provides some flags which you can control which allows you to influence if any memory is reported as leaked. This is in addition to the CRT marking its own allocations as “ignore”. You can set these flags using the _CrtSetDbgFlag(int); function.
The following flags can be passed to _CrtSetDbgFlag() in any combination.
Flag | Default | Meaning |
---|---|---|
_CRTDBG_ALLOC_MEM_DF | On | On: Enable debug heap allocations and use of memory block type identifiers. |
_CRTDBG_CHECK_ALWAYS_DF | Off | On: Call _CrtCheckMemory at every allocation and deallocation request. (Very slow!) |
_CRTDBG_CHECK_CRT_DF | Off | On: Include _CRT_BLOCK types in leak detection and memory state difference operations. |
_CRTDBG_DELAY_FREE_MEM_DF | Off | Keep freed memory blocks in the heap’s linked list, assign them the _FREE_BLOCK type, and fill them with the byte value 0xDD. CAUTION! Using this option will use lots of memory. |
_CRTDBG_LEAK_CHECK_DF | Off | ON: Perform automatic leak checking at program exit via a call to _CrtDumpMemoryLeaks and generate an error report if the application failed to free all the memory it allocated. |
How do I disable memory tracking for the CRT?
If you call _CrtSetDbgFlag(0); any memory allocated after that point will not be tracked.
With the above settings, all blocks are marked as ignore. You can see the code for this in the Microsoft C runtime.
The code that marks the block as “ignore” is at line 404 in dbgheap.c in the Microsoft C runtime (also used by MFC). When your code arrives here, nBLockUse == 1 and _crtDbgFlag == 0.
dbgheap.c line 404 (line number will vary with Visual Studio version) if (_BLOCK_TYPE(nBlockUse) != _CRT_BLOCK && !(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF)) fIgnore = TRUE;
This sets fIgnore to TRUE. From this point onwards the memory tracking code ignores the memory and sets the values mentioned above in the memory block header.
Default values
The default value for _crtDbgFlag is set elsewhere in the Microsoft code with this line:
extern "C" int _crtDbgFlag = _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_DEFAULT_DF;