PageHeap Notes |
|
Most of the corruptions in heap blocks can be discovered in one of two ways:
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 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.
To enable system-wide normal page heap
gflags -r +hpa
To disable system-wide normal page heap
gflags -r -hpa
Note
The Windows heap managers (all versions) have always guaranteed
that the heap allocations have a start address that is
If the user-requested size for the block is not
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
Note
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.
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.
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.
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:
Any other allocation/free set of functions probably is a custom scheme and is not guaranteed to call either directly or indirectly into NT heap. Only source code inspection or running under debugger can reveal the actual implementation.
Avoid using static linking. Some applications have been statically linked to old versions of the C runtime. These old versions do not call NT heap APIs, and PageHeap cannot be used to verify these allocations. Dynamic linking ensures that you get the latest C runtime library (msvcrt.dll).
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.
free()
class of interfaces. The page heap manager
immediately detects an invalid heap block pointer. See Debugging Page Heap Failures for a way
to determine if the invalid address is a few bytes off, or is
completely incorrect.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) |
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 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.
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
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 -?
Command | Description |
---|---|
!heap -p | Dumps addresses of all full page heaps created in the process. |
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. |
There are several failure scenarios that you may encounter. Some of them require additional detective work to get the whole picture.
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
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
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.