Windows Tools

PageHeap Notes

Overview | Notes | Syntax | Examples | Related Tools

Choosing a Method to Investigate Heap Block Corruptions

Most of the corruptions in heap blocks can be discovered in one of two ways:

  1. Full-page heap: Placing a non-accessible page at the end of the allocation.
  2. Normal page heap: Checking fill patterns when the block gets freed.

Full-Page Heap

Full-page heap should be enabled for individual processes, or under limited parameters for large processes, because of its high memory requirements. It cannot be enabled system-wide, because it is difficult to evaluate the required page file size. Using a page file that is too small with system-wide full-page heap renders the system unbootable.

The advantage of full-page heap is that it causes a process to access violate (AV) exactly at the point of failure. This makes the failure easy to debug. In order to be able to pinpoint failures, first use normal page heap to determine the range where a process is failing, and then use full-page heap on individual large-scale processes for that restricted class of allocations (that is, a specific size range or a specific library).

Normal Page Heap

Normal page heap can be used for the testing of large-scale processes without the high memory consumption that full-page heap requires. However, normal page heap delays detection until blocks are freed, thus making failures more difficult to debug.

In general, use normal page heap for initial large-scale processes testing. Then, if problems are detected, enable full-page heap for a restricted class of allocations in those processes.

Normal page heap can be safely enabled system-wide for all processes. This is very useful on test benches that perform general system validation, rather than component focused testing. Normal page heap can also be enabled for a single process.

Using GFlags with System-Wide Page Heap

The GFlags tool is used to enable system-wide page heap. In order for a GFlags command to take effect, you must restart your computer after you issue the command.

To enable system-wide normal page heap

  1. Type the following at the command line:
    gflags -r +hpa
  2. Restart your computer.

To disable system-wide normal page heap

  1. Type the following at the command line:
    gflags -r -hpa
  2. Restart your computer.

Note

Unaligned Allocations

The Windows heap managers (all versions) have always guaranteed that the heap allocations have a start address that is 8-byte aligned (on 64-bit platforms the alignment is 16-bytes). The page heap manager makes the same guarantee. This is impossible, however, if you want to have the end-of-the-allocation exactly at the end of a page. The exact end-of-page allocation is needed so that an off-by-one-byte error will trigger a read or write into the non-accessible page and cause an immediate fault.

If the user-requested size for the block is not 8-byte aligned, then page heap cannot meet both the "start address 8-byte aligned" and the "end address page aligned" constraints. The solution is to meet the first constraint and make the start of the block 8-byte aligned. Then use a small fill pattern between the end of the block and the start of the non-accessible page. This fill pattern can be from 0 bytes through 7 bytes in length on 32-bit architectures. The fill pattern is checked upon free.

If immediate fault detection is needed for these allocations that otherwise will have a fill pattern at the end, then make the page heap manager ignore the 8-byte alignment rule, and always align the end of the allocation at a page boundary by using the /unaligned and /full parameters. For more information, see the /unaligned parameter.

Note

Uncommitted Pages for Full-Page Heap Allocations

The core full-page heap implementation commits two pages for any allocation smaller than one page. One page is used for the user allocation, and the other is made non-accessible at the end of the buffer.

End-of-buffer overruns can be detected by using a zone of reserved virtual space, instead of a non-accessible committed page. An access violation exception occurs when the process touches that reserved virtual space. This approach can reduce memory consumption by up to 50 percent. For more information, see the /decommit parameter.

Fault Injection

You can control page heap manager so that some allocations are deliberately failed. This is useful in simulating low memory conditions without actually using all system resources.

Specify a number from 1 through 10000 to represent the probability that an allocation will fail. Using a probability of 10000 ensures that 100 percent of allocations will fail. A probability of 2000 specifies that approximately 20 percent of allocations will fail.

The page heap manager takes special care to avoid fault injection in both the first five seconds of the process’ life, and the Windows NT loader code paths (for exampole, LoadLibrary, FreeLibrary). If five seconds is not sufficient to allow your process to complete startup, then ou can specify a longer timeout at the beginning of the process. For more information, see the /fault parameter.

When you use the /fault parameter and the process being tested has a bug, an exception will be raised. Generally, the reason for this is that the allocate operation returned a NULL, and the application later tries to access the allocated memory. Since the allocate failed, however, the memory cannot be accessed, and therefore an access violation occurs.

The other reason that an exception is raised is that the application tries to deal with the allocation failure, but does not release some resources. This manifests as a memory leak and is more difficult to debug.

In order to help diagnose these failures, the page heap manager keeps a history of stack traces from the moment of fault injection. These traces can be displayed with the following debugger command:

!heap –p –f [NUMBER-OF-TRACES]

By default the extension will display only the last four traces.

Automatically Attaching a Debugger When the Application Starts

Some applications are difficult to start from a command prompt, or they are spawned from other processes. For these applications, specify that, whenever they are started, a debugger will automatically be attached to them. This is useful if page heap is enabled for that process and heap failures occur. For more information, see the /debug parameter.

What Interfaces Does PageHeap Verify?

PageHeap is effective when used to verify any memory allocation process, including C++ style allocations new and delete, as long as the custom allocation/free functions eventually call into NT heap management interfaces (that is, RtlAllocateHeap, RtlFreeHeap). The following functions are guaranteed to do that:

Classes of Bugs Found by PageHeap

PageHeap detects most heap-related bugs; however, it is focused on heap corruption problems and not focused on leaks. PageHeap has limited success with finding heap leaks, although it has functionality to target this.

One of the advantages of PageHeap is that many errors are detected when they happen. For example, an off-by-one-byte error at the end of a dynamically allocated buffer might cause an instant access violation. There are few types of errors that cannot be detected when they occur. In those cases, the error report is delayed until the block is freed.

Failure Normal page heap Full-page heap
Invalid heap pointer Caught instantly Caught instantly
Invalid heap block pointer Caught instantly Caught instantly
Unsynchronized access Caught instantly Caught instantly
Assumption about reallocation address 90% until real free 90% caught instantly
Double free 90% caught instantly 90% caught instantly
Reuse after free 90% until real free 90% caught instantly
Access after end of block Caught upon free Caught instantly
Access before start of block Caught upon free Caught instantly (special flag)

Debugging Page Heap Failures

With full-page heap enabled for end-of-buffer-overrun bug detection, an application will access violate at the moment it tries to access memory past the end of the page. These failures are easy to debug because the current stack trace points directly to the failing code.

If normal page heap is used, or if the corruption happens in the small fill pattern at the end of the buffer for alignment reasons, then the corruption will be detected only when the block is freed. In these cases, additional debugging is required to identify the problem.

The Structure of a Page Heap Block

The page heap manager places a header at the beginning of all allocations, both full and normal. This header contains valuable information: the owning heap, user requested size, and in some cases, stack trace for the allocation.

Diagram of a normal page heap block structure Diagram of a full page heap block structure

The information block has the following structure:

DPH_BLOCK_INFORMATION

ULONG			 StartStamp;
PVOID		 Heap;
SIZE_T		 RequestedSize;
SIZE_T		 ActualSize;
LIST_ENTRY	FreeQueue;
PVOID		 StackTrace;
ULONG		 EndStamp;

The Heap field stores the owning heap. The user-requested size for the block is in RequestedSize. The stack can be obtained by issuing a dds debugger command at StackTrace address. The best way to get a dump of this structure is to use the following dd debugger command:

dd ADDRESS L20

Or if you have the latest NT debugger package and PDB symbols for ntdll.dll, then use the following:

dt ntdll!_DPH_BLOCK_INFORMATION ADDRESS

The StackTrace field does not always contain a non-null value. Stack trace detection is only supported on x86 platforms, but even on x86 machines the stack trace detection algorithms are not completely reliable. If the block is an allocated block, then the stack trace is for the allocation moment. If the block was freed, then the stack trace is for the free moment.

The Page Heap Debugger Extension

The page heap debugger extension is part of the !heap extension (Windows NT heap debugger extension). Simple help can be obtained by typing the following from within the debugger:

!heap -?

More extensive information is available by typing the following:

!heap -p -?

The extension is not intelligent enough to detect on its own if page heap is enabled for a process. The user needs to know that page heap is enabled and use commands prefixed by !heap -p.
Command Description
!heap -p Dumps addresses of all full page heaps created in the process.
!heap -p -h HEAP-ADDRESS Full dump of full page heap at HEAP-ADDRESS.
!heap -p -a ADDRESS Tries to determine if there is a heap block at ADDRESS. This value does not need to be the address of the start of the block. The command is useful if you have no information about the nature of a memory area.
!heap -p -t NUMBER This command dumps the first NUMBER heavy users of heap allocations. This command is useful for leak analysis.

Typical Debug Scenarios

There are several failure scenarios that you may encounter. Some of them require additional detective work to get the whole picture.

Access violation in a non-accessible page

This happens when full-page heap is enabled and the tested application accesses heap beyond the end of the buffer. It can also happen when the application touches a freed block. In order to understand the nature of the address on which the exception happened, use this command in the debugger:

!heap -p -f ADDRESS-OF-AV

Corrupted block message

At several moments during the lifetime of an allocation (allocation, user free, real free), the page heap manager checks to see if the block has all fill patterns intact, and if the block header has consistent data. If this is not the case you will get the message "Page heap: block @ ADDRESS is corrupted".

If this is a full-page heap block (that is, if you know that full-page heap is enabled for all allocations), then you can use this command to find the characteristics of the block:

!heap -p -f ADDRESS

If it is a normal page heap block, then you must determine the start address for the block header. To do this, dump 30-40 bytes below the reported address and look for the start/end patterns for a block header: ABCDAAAA, ABCDBBBB, ABCDAAA9, ABCDBBBA. See The Structure of a Page Heap Block for more information about the structure of the heap.

The header provides most of the information that you need to understand a failure. Specifically the start/end patterns tell you if the block is allocated or free, and if it is a normal or full-page heap block. The information has to be matched carefully with the offending call. For example, if a call to HeapFree is made with the address of a block plus four bytes, then you will get the corrupted block message. The block header will look fine, but the first byte after the end of the header (that is, the first byte after 0xDCBAxxxx) has a different address then the one in the call.

Note

  • Even if you have full-page heap enabled, you can get the corrupted block message if there is an "off-by-a-few-bytes" error because of the small fill pattern needed at end of allocations for alignment reasons. See "Unaligned allocations" earlier in this document.

Special fill pointers

The page heap manager fills the user allocation with values that look like kernel pointers (fill value is F0). This occurs when the block is freed and if the block is allocated, but no request is made for the block to be zeroed (fill value is E0 for normal page heap and C0 for full-page heap). The non-zeroed allocations are typical for malloc/new users. If there is an access violation where a read/write is attempted at addresses like 0xF0F0F0F0, 0xE0E0E0E0, 0xC0C0C0C0, then the test application has probably hit one of these cases.

A read/write at 0xF0F0F0F0 means that a block has been used after it was freed. You need to do some detective work to find out which block caused this. Inspect the stack trace of the failure and the code for the functions on the stack. Look for functions that make wrong assumptions about an allocation being available after it was freed.

A read/write at 0xE0E0E0E0 or 0xC0C0C0C0 means that the application did not initialize the allocation properly. Inspect the code of the functions in the current stack trace.