Andrew Birkett's nobugs.org
… by Andrew Birkett (andy@nobugs.org)
If you are lazy, skip the explanation and jump to the table at the bottom of the page
When you compile programs with DevStudio in debug mode, all of your calls to malloc() and free() use a special “debug” implementation.
Rather than being blazingly fast, the debug heap concerns itself with spotting heap errors. It achieves this by surrounding your memory blocks with guard bytes (aka “no mans land”, 0xFD) so that it can detect buffer overruns and underruns. It also initialises newly allocated memory to a fixed value (0xCD) to aid reproducability. Finally, it sets free()d blocks to a known value (0xDD) so that it can detect that people are writing through dangling pointers.
Mnemonics for remembering what each fill-pattern means:
The debug CRT heap defers most of the heavy work to the Win32 heap functions HeapAlloc() and HeapFree(). Therefore, you won’t see any first-fit or “buddy system” code in the debug CRT. The 4Gb virtual memory space which you process has is sliced up and managed by the Win32 heap inside kernel32.dll.
If you call malloc(8), the debug CRT will request a 48 byte block from HeapAlloc(). It needs the extra 40 bytes to store information about the memory blocks - such as the file/line where malloc() was called, and space for links to the next/prev heap block. In the table below, all of the debug CRT information is colored in shades of red.
HeapAlloc() itself needs bookkeeping information. The HeapAlloc(40) call will, in turn, reserve a total of 80 bytes from your process’s address space. Eight bytes of bookkeeping appear below the requested 40 bytes, and the other 32 bytes appear above it. In the table below, the Win32 heap bookkeeping is colored grey. The memory which you get back from HeapAlloc() is always initialised to the 4 byte pattern 0xBAADF00D.
(As an aside, when you request pages from the VM manager via VirtualAlloc, they are initialized to zero, so HeapAlloc is actually doing additional work to re-initialize them to this pattern).
Once the debug CRT has got it’s 40 byte block, it will fill in it’s book-keeping information. The first 2 words are links to the previous and next blocks on the CRT heap. The choice of names is confusing, because the “next” pointer actually takes you the block which was allocated before this one chronologically, while the “previous” pointer takes you to the one allocated subsequently. The reason for the naming is that the linked list starts at the last-allocated block, and progresses back in time as you follow “next” links. The debug CRT heap also internally maintains pointers to the first and last blocks (_pFirstBlock and _pLastBlock) to allow heap-checking code to traverse all the blocks.
If the filename and line number of the malloc() call are known, they are stored in the next 2 words. Following that, the next word tells you how many bytes were requested. The next word gives a type field. Usually this is “1” which means a normal block, allocated by malloc/new. It will be “2” for blocks allocate by the CRT for its own internal purposes, and “0” for blocks which have been freed but not released back to the win32 heap (see below for more info). The final word is a simple counter which increases everytime an allocation is made.
Surrounding the 8-byte malloc()’d memory there are areas of “no mans land”. These are filled with a known value (0xFD), and when the block is free()d, the CRT will check that they still have the right value. If they’ve changed, then your program contains an error. Unfortunately, the corruption may have happened a long time ago. You can use Purify or Boundschecker to stop at the corruption point, or if you don’t fancy spending any money, you can wait a few days until I write an article telling you how to do it using only a bit of ingenuity!
The eight bytes which were originally requested are initialised with 0xCD. If you see this pattern appearing in your variables, you have forgotten to initialise something.
When you call free() on the 8-byte block, the CRT sets the whole 48-byte block (including its bookkeeping) to 0xDDDDDDDD. This means that it can tell if the block gets subsequently altered (ie. via a dangling pointer) by checking that the pattern is still there.
At this point, two things can happen. Normally, HeapFree() will be called to return the block to the win32 heap. This causes the block to be overwritten with the win32 heap’s “freed memory” pattern, which is 0xFEEEFEEE. Note that the debug CRT does not maintain any “free lists” - all of that is done within the black box of HeapFree().
However, you can also tell the debug heap to keep hold of free()d blocks. You do this by passing the _CRTDBG_DELAY_FREE_MEM_DF flag to _CrtSetDbgFlag(). In this case, the debug CRT will keep hold of the block. This is useful if you are trying to track down a dangling pointer error, since memory blocks will not be reused and you should expect them to remain filled with 0xDDDDDDDD unless someone is writing to the free()d block. You can call _CrtCheckMemory() and it will tell you if any of these values have been tampered with.
h2. Here’s an allocation I prepared earlier …
I called malloc(8) followed by free() and stepped through the CRT calls to see how the memory was changed. Read the columns from left to right, and you will see what values appear in memory at various stages during malloc() and free(). The call to malloc(8) returned a block at address 0x00321000, and I’ve included offsets from that address so that you can find out the information for one of your allocations.
Address | Offset | After HeapAlloc() | After malloc() | During free() | After HeapFree() | Comments |
0x00320FD8 | -40 | 0x01090009 | 0x01090009 | 0x01090009 | 0x0109005A | Win32 heap info |
0x00320FDC | -36 | 0x01090009 | 0x00180700 | 0x01090009 | 0x00180400 | Win32 heap info |
0x00320FE0 | -32 | 0xBAADF00D | 0x00320798 | 0xDDDDDDDD | 0x00320448 | Ptr to next CRT heap block (allocated earlier in time) |
0x00320FE4 | -28 | 0xBAADF00D | 0x00000000 | 0xDDDDDDDD | 0x00320448 | Ptr to prev CRT heap block (allocated later in time) |
0x00320FE8 | -24 | 0xBAADF00D | 0x00000000 | 0xDDDDDDDD | 0xFEEEFEEE | Filename of malloc() call |
0x00320FEC | -20 | 0xBAADF00D | 0x00000000 | 0xDDDDDDDD | 0xFEEEFEEE | Line number of malloc() call |
0x00320FF0 | -16 | 0xBAADF00D | 0x00000008 | 0xDDDDDDDD | 0xFEEEFEEE | Number of bytes to malloc() |
0x00320FF4 | -12 | 0xBAADF00D | 0x00000001 | 0xDDDDDDDD | 0xFEEEFEEE | Type (0=Freed, 1=Normal, 2=CRT use, etc) |
0x00320FF8 | -8 | 0xBAADF00D | 0x00000031 | 0xDDDDDDDD | 0xFEEEFEEE | Request #, increases from 0 |
0x00320FFC | -4 | 0xBAADF00D | 0xFDFDFDFD | 0xDDDDDDDD | 0xFEEEFEEE | No mans land |
0x00321000 | +0 | 0xBAADF00D | 0xCDCDCDCD | 0xDDDDDDDD | 0xFEEEFEEE | The 8 bytes you wanted |
0x00321004 | +4 | 0xBAADF00D | 0xCDCDCDCD | 0xDDDDDDDD | 0xFEEEFEEE | The 8 bytes you wanted |
0x00321008 | +8 | 0xBAADF00D | 0xFDFDFDFD | 0xDDDDDDDD | 0xFEEEFEEE | No mans land |
0x0032100C | +12 | 0xBAADF00D | 0xBAADF00D | 0xDDDDDDDD | 0xFEEEFEEE | Win32 heap allocations are rounded up to 16 bytes |
0x00321010 | +16 | 0xABABABAB | 0xABABABAB | 0xABABABAB | 0xFEEEFEEE | Win32 heap bookkeeping |
0x00321014 | +20 | 0xABABABAB | 0xABABABAB | 0xABABABAB | 0xFEEEFEEE | Win32 heap bookkeeping |
0x00321018 | +24 | 0x00000010 | 0x00000010 | 0x00000010 | 0xFEEEFEEE | Win32 heap bookkeeping |
0x0032101C | +28 | 0x00000000 | 0x00000000 | 0x00000000 | 0xFEEEFEEE | Win32 heap bookkeeping |
0x00321020 | +32 | 0x00090051 | 0x00090051 | 0x00090051 | 0xFEEEFEEE | Win32 heap bookkeeping |
0x00321024 | +36 | 0xFEEE0400 | 0xFEEE0400 | 0xFEEE0400 | 0xFEEEFEEE | Win32 heap bookkeeping |
0x00321028 | +40 | 0x00320400 | 0x00320400 | 0x00320400 | 0xFEEEFEEE | Win32 heap bookkeeping |
0x0032102C | +44 | 0x00320400 | 0x00320400 | 0x00320400 | 0xFEEEFEEE | Win32 heap bookkeeping |
(I’ve tried to helpfully color-code things. Blue and grey is for Win32 heap stuff, and reds are for the debug crt heap stuff)
If you want to read more, check out Debugging Windows Programs , part of the DevelopMentor series.
Any comments or feedback? Please email me.