|
|
/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
heapleak.c
Abstract:
Garbage collection leak detection
Author:
Adrian Marinescu (adrmarin) 04-24-2000
Revision History:
--*/
#include "ntrtlp.h"
#include "heap.h"
#include "heappriv.h"
//
// heap walking contexts.
//
#define CONTEXT_START_GLOBALS 11
#define CONTEXT_START_HEAP 1
#define CONTEXT_END_HEAP 2
#define CONTEXT_START_SEGMENT 3
#define CONTEXT_END_SEGMENT 4
#define CONTEXT_FREE_BLOCK 5
#define CONTEXT_BUSY_BLOCK 6
#define CONTEXT_LOOKASIDE_BLOCK 7
#define CONTEXT_VIRTUAL_BLOCK 8
#define CONTEXT_END_BLOCKS 9
#define CONTEXT_ERROR 10
typedef BOOLEAN (*HEAP_ITERATOR_CALLBACK)( IN ULONG Context, IN PHEAP HeapAddress, IN PHEAP_SEGMENT SegmentAddress, IN PHEAP_ENTRY EntryAddress, IN ULONG_PTR Data );
//
// Garbage collector structures
//
typedef enum _USAGE_TYPE {
UsageUnknown, UsageModule, UsageHeap, UsageOther
} USAGE_TYPE;
typedef struct _HEAP_BLOCK {
LIST_ENTRY Entry; ULONG_PTR BlockAddress; ULONG_PTR Size; LONG Count;
} HEAP_BLOCK, *PHEAP_BLOCK;
typedef struct _BLOCK_DESCR {
USAGE_TYPE Type; ULONG_PTR Heap; LONG Count; HEAP_BLOCK Blocks[1];
}BLOCK_DESCR, *PBLOCK_DESCR;
typedef struct _MEMORY_MAP {
ULONG_PTR Granularity; ULONG_PTR Offset; ULONG_PTR MaxAddress;
CHAR FlagsBitmap[256 / 8];
union{ struct _MEMORY_MAP * Details[ 256 ]; PBLOCK_DESCR Usage[ 256 ]; };
struct _MEMORY_MAP * Parent;
} MEMORY_MAP, *PMEMORY_MAP;
//
// Process leak detection flags
//
#define INSPECT_LEAKS 1
#define BREAK_ON_LEAKS 2
ULONG RtlpShutdownProcessFlags = 0;
//
// Allocation routines. It creates a temporary heap for the temporary
// leak detection structures
//
HANDLE RtlpLeakHeap;
#define RtlpLeakAllocateBlock(Size) RtlAllocateHeap(RtlpLeakHeap, 0, Size)
//
// Local data declarations
//
MEMORY_MAP RtlpProcessMemoryMap; LIST_ENTRY RtlpBusyList; LIST_ENTRY RtlpLeakList;
ULONG RtlpLeaksCount = 0;
ULONG_PTR RtlpLDPreviousPage = 0; ULONG_PTR RtlpLDCrtPage = 0; LONG RtlpLDNumBlocks = 0; PHEAP_BLOCK RtlpTempBlocks = NULL; ULONG_PTR RtlpCrtHeapAddress = 0; ULONG_PTR RtlpLeakHeapAddress = 0; ULONG_PTR RtlpPreviousStartAddress = 0;
//
// Debugging facility
//
ULONG_PTR RtlpBreakAtAddress = MAXULONG_PTR;
//
// Walking heap routines. These are general purposes routines that
// receive a callback function to handle a specific operation
//
BOOLEAN RtlpReadHeapSegment( IN PHEAP Heap, IN ULONG SegmentIndex, IN PHEAP_SEGMENT Segment, IN HEAP_ITERATOR_CALLBACK HeapCallback )
/*++
Routine Description:
This routine is called to walk a heap segment. For each block from the segment is invoked the HeapCallback function.
Arguments:
Heap - The heap being walked SegmentIndex - The index of this segment Segment - The segment to be walked HeapCallback - a HEAP_ITERATOR_CALLBACK function passed down from the heap walk
Return Value:
TRUE if succeeds.
--*/
{ PHEAP_ENTRY PrevEntry, Entry, NextEntry; PHEAP_UNCOMMMTTED_RANGE UnCommittedRange; ULONG_PTR UnCommittedRangeAddress = 0; SIZE_T UnCommittedRangeSize = 0;
//
// Ask the callback if we're required to walk this segment. Return otherwise.
//
if (!(*HeapCallback)( CONTEXT_START_SEGMENT, Heap, Segment, 0, 0 )) {
return FALSE; }
//
// Prepare to read the uncommitted ranges. we need to jump
// to the next uncommitted range for each last block
//
UnCommittedRange = Segment->UnCommittedRanges;
if (UnCommittedRange) {
UnCommittedRangeAddress = (ULONG_PTR)UnCommittedRange->Address; UnCommittedRangeSize = UnCommittedRange->Size; } //
// Walk the segment, block by block
//
Entry = (PHEAP_ENTRY)Segment->BaseAddress; PrevEntry = 0;
while (Entry < Segment->LastValidEntry) {
ULONG EntryFlags = Entry->Flags;
//
// Determine the next block entry. Size is in heap granularity and
// sizeof(HEAP_ENTRY) == HEAP_GRANULARITY.
//
NextEntry = Entry + Entry->Size;
(*HeapCallback)( (Entry->Flags & HEAP_ENTRY_BUSY ? CONTEXT_BUSY_BLOCK : CONTEXT_FREE_BLOCK), Heap, Segment, Entry, Entry->Size );
PrevEntry = Entry; Entry = NextEntry; //
// Check whether this is the last entry
//
if (EntryFlags & HEAP_ENTRY_LAST_ENTRY) {
if ((ULONG_PTR)Entry == UnCommittedRangeAddress) {
//
// Here we need to skip the uncommited range and jump
// to the next valid block
//
PrevEntry = 0; Entry = (PHEAP_ENTRY)(UnCommittedRangeAddress + UnCommittedRangeSize);
UnCommittedRange = UnCommittedRange->Next; if (UnCommittedRange) {
UnCommittedRangeAddress = UnCommittedRange->Address; UnCommittedRangeSize = UnCommittedRange->Size; }
} else {
//
// We finished the search because we exausted the uncommitted
// ranges
//
break; } } }
//
// Return to our caller.
//
return TRUE; }
BOOLEAN RtlpReadHeapData( IN PHEAP Heap, IN HEAP_ITERATOR_CALLBACK HeapCallback )
/*++
Routine Description:
This routine is called to walk a heap. This means: - walking all segments - walking the virtual blocks - walking the lookaside
Arguments:
Heap - The heap being walked HeapCallback - a HEAP_ITERATOR_CALLBACK function passed down from the heap walk
Return Value:
TRUE if succeeds.
--*/
{ ULONG SegmentCount; PLIST_ENTRY Head, Next; PHEAP_LOOKASIDE Lookaside = (PHEAP_LOOKASIDE)RtlpGetLookasideHeap(Heap);
//
// Flush the lookaside first
//
if (Lookaside != NULL) {
ULONG i; PVOID Block;
Heap->FrontEndHeap = NULL; Heap->FrontEndHeapType = 0;
for (i = 0; i < HEAP_MAXIMUM_FREELISTS; i += 1) {
while ((Block = RtlpAllocateFromHeapLookaside(&(Lookaside[i]))) != NULL) {
RtlFreeHeap( Heap, 0, Block ); } } }
//
// Check whether we're required to walk this heap
//
if (!(*HeapCallback)( CONTEXT_START_HEAP, Heap, 0, 0, 0 )) {
return FALSE; } //
// Start walking through the segments
//
for (SegmentCount = 0; SegmentCount < HEAP_MAXIMUM_SEGMENTS; SegmentCount++) { PHEAP_SEGMENT Segment = Heap->Segments[SegmentCount];
if (Segment) { //
// Call the appropriate routine to walk a valid segment
//
RtlpReadHeapSegment( Heap, SegmentCount, Segment, HeapCallback ); } }
//
// Start walking the virtual block list
//
Head = &Heap->VirtualAllocdBlocks;
Next = Head->Flink; while (Next != Head) {
PHEAP_VIRTUAL_ALLOC_ENTRY VirtualAllocBlock;
VirtualAllocBlock = CONTAINING_RECORD(Next, HEAP_VIRTUAL_ALLOC_ENTRY, Entry);
(*HeapCallback)( CONTEXT_VIRTUAL_BLOCK, Heap, 0, NULL, (ULONG_PTR)VirtualAllocBlock );
Next = Next->Flink; }
if (!(*HeapCallback)( CONTEXT_END_BLOCKS, Heap, 0, 0, 0 )) {
return FALSE; }
return TRUE; }
VOID RtlpReadProcessHeaps( IN HEAP_ITERATOR_CALLBACK HeapCallback )
/*++
Routine Description:
This routine is called to walk the existing heaps in the current process Arguments:
HeapCallback - a HEAP_ITERATOR_CALLBACK function passed down from the heap walk
Return Value:
TRUE if succeeds.
--*/
{ ULONG i; PPEB ProcessPeb = NtCurrentPeb();
if (!(*HeapCallback)( CONTEXT_START_GLOBALS, 0, 0, 0, (ULONG_PTR)ProcessPeb )) {
return; } //
// Walk the heaps from the process PEB
//
for (i = 0; i < ProcessPeb->NumberOfHeaps; i++) {
RtlpReadHeapData ( (PHEAP)(ProcessPeb->ProcessHeaps[ i ]), HeapCallback ); } }
VOID RtlpInitializeMap ( IN PMEMORY_MAP MemMap, IN PMEMORY_MAP Parent )
/*++
Routine Description:
This routine initialize a memory map structure Arguments:
MemMap - Map being initializated Parent - The upper level map
Return Value:
None
--*/
{ //
// Clear the memory map data
//
RtlZeroMemory(MemMap, sizeof(*MemMap));
//
// Save the upper level map
//
MemMap->Parent = Parent;
//
// Determine the granularity from the parent's granularity
//
if (Parent) {
MemMap->Granularity = Parent->Granularity / 256; } }
VOID RtlpSetBlockInfo ( IN PMEMORY_MAP MemMap, IN ULONG_PTR Base, IN ULONG_PTR Size, IN PBLOCK_DESCR BlockDescr )
/*++
Routine Description: The routine will set a given block descriptor for a range in the memory map. Arguments:
MemMap - The memory map Base - base address for the range to be set. Size - size in bytes of the zone BlockDescr - The pointer to the BLOCK_DESCR structure to be set
Return Value:
None
--*/
{ ULONG_PTR Start, End; ULONG_PTR i; //
// Check wheter we got a valid range
//
if (((Base + Size - 1) < MemMap->Offset) || (Base > MemMap->MaxAddress) ) {
return; }
//
// Determine the starting index to be set
//
if (Base > MemMap->Offset) { Start = (Base - MemMap->Offset) / MemMap->Granularity; } else { Start = 0; }
//
// Determine the ending index to be set
//
End = (Base - MemMap->Offset + Size - 1) / MemMap->Granularity;
if (End > 255) {
End = 255; }
for (i = Start; i <= End; i++) {
//
// Check whether this is the lowes memory map level
//
if (MemMap->Granularity == PAGE_SIZE) {
//
// This is the last level in the memory map, so we can apply
// the block descriptor here
//
if (BlockDescr) {
//
// Check if we already have a block descriptor here
//
if (MemMap->Usage[i] != NULL) { if (MemMap->Usage[i] != BlockDescr) {
DbgPrint("Error\n"); } }
//
// Assign the given descriptor
//
MemMap->Usage[i] = BlockDescr;
} else {
//
// We didn't recedive a block descriptor. We set
// then the given flag
//
MemMap->FlagsBitmap[i / 8] |= 1 << (i % 8); }
} else {
//
// This isn't the lowest map level. We recursively call
// this function for the next detail range
//
if (!MemMap->Details[i]) {
//
// Allocate a new map
//
MemMap->Details[i] = RtlpLeakAllocateBlock( sizeof(*MemMap) );
if (!MemMap->Details[i]) { DbgPrint("Error allocate\n"); }
//
// Initialize the map and link it with the current one
//
RtlpInitializeMap(MemMap->Details[i], MemMap); MemMap->Details[i]->Offset = MemMap->Offset + MemMap->Granularity * i; MemMap->Details[i]->MaxAddress = MemMap->Offset + MemMap->Granularity * (i+1) - 1; } RtlpSetBlockInfo(MemMap->Details[i], Base, Size, BlockDescr); } } }
PBLOCK_DESCR RtlpGetBlockInfo ( IN PMEMORY_MAP MemMap, IN ULONG_PTR Base )
/*++
Routine Description:
This function will return the appropriate Block descriptor for a given base address Arguments:
MemMap - The memory map Base - The base address for the descriptor we are looking for
Return Value:
None
--*/
{ ULONG_PTR Start; PBLOCK_DESCR BlockDescr = NULL; //
// Validate the range
//
if ((Base < MemMap->Offset) || (Base > MemMap->MaxAddress) ) {
return NULL; }
//
// Determine the appropriate index for lookup
//
if (Base > MemMap->Offset) { Start = (Base - MemMap->Offset) / MemMap->Granularity; } else { Start = 0; } //
// If this is the lowest map level we'll return that entry
//
if (MemMap->Granularity == PAGE_SIZE) {
return MemMap->Usage[ Start ];
} else {
//
// We need a lower detail level call
//
if (MemMap->Details[ Start ]) {
return RtlpGetBlockInfo( MemMap->Details[Start], Base ); } }
//
// We didn't find something for this address, we'll return NULL then
//
return NULL; }
BOOLEAN RtlpGetMemoryFlag ( IN PMEMORY_MAP MemMap, IN ULONG_PTR Base )
/*++
Routine Description:
This function returns the flag for a given base address Arguments:
MemMap - The memory map Base - The base address we want to know the flag
Return Value:
None
--*/
{ ULONG_PTR Start; PBLOCK_DESCR BlockDescr = NULL; //
// Validate the base address
//
if ((Base < MemMap->Offset) || (Base > MemMap->MaxAddress) ) {
return FALSE; }
//
// Determine the appropriate index for the given base address
//
if (Base > MemMap->Offset) {
Start = (Base - MemMap->Offset) / MemMap->Granularity;
} else {
Start = 0; }
if (MemMap->Granularity == PAGE_SIZE) {
//
// Return the bit value if are in the case of
// the lowest detail level
//
return (MemMap->FlagsBitmap[Start / 8] & (1 << (Start % 8))) != 0;
} else {
//
// Lookup in the detailed map
//
if (MemMap->Details[Start]) {
return RtlpGetMemoryFlag(MemMap->Details[Start], Base); } }
return FALSE; }
VOID RtlpInitializeLeakDetection ()
/*++
Routine Description:
This function initialize the leak detection structures Arguments:
Return Value:
None
--*/
{ ULONG_PTR AddressRange = PAGE_SIZE; ULONG_PTR PreviousAddressRange = PAGE_SIZE;
//
// Initialize the global memory map
//
RtlpInitializeMap(&RtlpProcessMemoryMap, NULL);
//
// Initialize the lists
//
InitializeListHead( &RtlpBusyList ); InitializeListHead( &RtlpLeakList ); //
// Determine the granularity for the highest memory map level
//
while (TRUE) {
AddressRange = AddressRange * 256;
if (AddressRange < PreviousAddressRange) {
RtlpProcessMemoryMap.MaxAddress = MAXULONG_PTR;
RtlpProcessMemoryMap.Granularity = PreviousAddressRange;
break; } PreviousAddressRange = AddressRange; }
RtlpTempBlocks = RtlpLeakAllocateBlock(PAGE_SIZE); }
BOOLEAN RtlpPushPageDescriptor( IN ULONG_PTR Page, IN ULONG_PTR NumPages )
/*++
Routine Description:
This routine binds the temporary block data into a block descriptor structure and push it to the memory map Arguments:
Page - The start page that wil contain this data NumPages - The number of pages to be set
Return Value:
TRUE if succeeds.
--*/
{ PBLOCK_DESCR PBlockDescr; PBLOCK_DESCR PreviousDescr;
//
// Check whether we already have a block descriptor there
//
PreviousDescr = RtlpGetBlockInfo( &RtlpProcessMemoryMap, Page * PAGE_SIZE );
if (PreviousDescr) {
DbgPrint("Conflicting descriptors %08lx\n", PreviousDescr);
return FALSE; }
//
// We need to allocate a block descriptor structure and initializate it
// with the acquired data.
//
PBlockDescr = (PBLOCK_DESCR)RtlpLeakAllocateBlock(sizeof(BLOCK_DESCR) + (RtlpLDNumBlocks - 1) * sizeof(HEAP_BLOCK));
if (!PBlockDescr) {
DbgPrint("Unable to allocate page descriptor\n");
return FALSE; }
PBlockDescr->Type = UsageHeap; PBlockDescr->Count = RtlpLDNumBlocks; PBlockDescr->Heap = RtlpCrtHeapAddress;
//
// Copy the temporary block buffer
//
RtlCopyMemory(PBlockDescr->Blocks, RtlpTempBlocks, RtlpLDNumBlocks * sizeof(HEAP_BLOCK));
//
// If this page doesn't bnelong to the temporary heap, we insert all these blocks
// in the busy list
//
if (RtlpCrtHeapAddress != RtlpLeakHeapAddress) {
LONG i;
for (i = 0; i < RtlpLDNumBlocks; i++) {
InitializeListHead( &PBlockDescr->Blocks[i].Entry );
//
// We might have a blockin more different pages. but We'll
// insert only ones in the list
//
if (PBlockDescr->Blocks[i].BlockAddress != RtlpPreviousStartAddress) {
InsertTailList(&RtlpLeakList, &PBlockDescr->Blocks[i].Entry);
PBlockDescr->Blocks[i].Count = 0;
//
// Save the last block address
//
RtlpPreviousStartAddress = PBlockDescr->Blocks[i].BlockAddress; } } }
//
// Set the memory map with this block descriptor
//
RtlpSetBlockInfo(&RtlpProcessMemoryMap, Page * PAGE_SIZE, NumPages * PAGE_SIZE, PBlockDescr);
return TRUE; }
BOOLEAN RtlpRegisterHeapBlocks ( IN ULONG Context, IN PHEAP Heap OPTIONAL, IN PHEAP_SEGMENT Segment OPTIONAL, IN PHEAP_ENTRY Entry OPTIONAL, IN ULONG_PTR Data OPTIONAL )
/*++
Routine Description:
This is the callback routine invoked while parsing the process heaps. Depending on the context it is invoked it performs different tasks. Arguments:
Context - The context this callback is being invoked Heap - The Heap structure Segment - The current Segment (if any) Entry - The current block entry (if any) Data - Additional data
Return Value:
TRUE if succeeds.
--*/
{ //
// Check whether we need to break at this address
//
if ((ULONG_PTR)Entry == RtlpBreakAtAddress) {
DbgBreakPoint(); } if (Context == CONTEXT_START_HEAP) {
//
// The only thing we need to do in this case
// is to set the global current heap address
//
RtlpCrtHeapAddress = (ULONG_PTR)Heap;
return TRUE; } //
// For a new segment, we mark the flag for the whole
// reserved space for the segment the flag to TRUE
//
if (Context == CONTEXT_START_SEGMENT) {
RtlpSetBlockInfo(&RtlpProcessMemoryMap, (ULONG_PTR)Segment->BaseAddress, Segment->NumberOfPages * PAGE_SIZE, NULL); return TRUE; }
if (Context == CONTEXT_ERROR) { DbgPrint("HEAP %p (Seg %p) At %p Error: %s\n", Heap, Segment, Entry, Data );
return TRUE; }
if (Context == CONTEXT_END_BLOCKS) { if (RtlpLDPreviousPage) {
RtlpPushPageDescriptor(RtlpLDPreviousPage, 1); }
RtlpLDPreviousPage = 0; RtlpLDNumBlocks = 0;
} else if (Context == CONTEXT_BUSY_BLOCK) {
ULONG_PTR EndPage;
//
// EnrtySize is assuming is the same as heap granularity
//
EndPage = (((ULONG_PTR)(Entry + Entry->Size)) - 1)/ PAGE_SIZE;
//
// Check whether we received a valid block
//
if ((Context == CONTEXT_BUSY_BLOCK) && !RtlpGetMemoryFlag(&RtlpProcessMemoryMap, (ULONG_PTR)Entry)) {
DbgPrint("%p address isn't from the heap\n", Entry); }
//
// Determine the starting page that contains the block
//
RtlpLDCrtPage = ((ULONG_PTR)Entry) / PAGE_SIZE;
if (RtlpLDCrtPage != RtlpLDPreviousPage) {
//
// We moved to an other page, so we need to save the previous
// information before going further
//
if (RtlpLDPreviousPage) {
RtlpPushPageDescriptor(RtlpLDPreviousPage, 1); } //
// Reset the temporary data. We're starting a new page now
//
RtlpLDPreviousPage = RtlpLDCrtPage; RtlpLDNumBlocks = 0; }
//
// Add this block to the current list
//
RtlpTempBlocks[RtlpLDNumBlocks].BlockAddress = (ULONG_PTR)Entry; RtlpTempBlocks[RtlpLDNumBlocks].Count = 0; RtlpTempBlocks[RtlpLDNumBlocks].Size = Entry->Size * sizeof(HEAP_ENTRY);
RtlpLDNumBlocks++; if (EndPage != RtlpLDCrtPage) {
//
// The block ends on a different page. We can then save the
// starting page and all others but the last one
//
RtlpPushPageDescriptor(RtlpLDCrtPage, 1);
RtlpLDNumBlocks = 0;
RtlpTempBlocks[RtlpLDNumBlocks].BlockAddress = (ULONG_PTR)Entry; RtlpTempBlocks[RtlpLDNumBlocks].Count = 0; RtlpTempBlocks[RtlpLDNumBlocks].Size = Entry->Size * sizeof(HEAP_ENTRY);
RtlpLDNumBlocks += 1; if (EndPage - RtlpLDCrtPage > 1) { RtlpPushPageDescriptor(RtlpLDCrtPage + 1, EndPage - RtlpLDCrtPage - 1); } RtlpLDPreviousPage = EndPage; }
} else if (Context == CONTEXT_VIRTUAL_BLOCK) {
PHEAP_VIRTUAL_ALLOC_ENTRY VirtualAllocBlock = (PHEAP_VIRTUAL_ALLOC_ENTRY)Data; ULONG_PTR EndPage;
//
// EnrtySize is assuming is the same as heap granularity
//
EndPage = ((ULONG_PTR)Data + VirtualAllocBlock->CommitSize - 1)/ PAGE_SIZE;
RtlpLDCrtPage = (Data) / PAGE_SIZE;
if (RtlpLDCrtPage != RtlpLDPreviousPage) {
//
// Save the previous data if we're moving to a new page
//
if (RtlpLDPreviousPage) {
RtlpPushPageDescriptor(RtlpLDPreviousPage, 1); } RtlpLDPreviousPage = RtlpLDCrtPage; RtlpLDNumBlocks = 0; }
//
// Initialize the block descriptor structure as we are
// starting a new page
//
RtlpLDNumBlocks = 0;
RtlpTempBlocks[RtlpLDNumBlocks].BlockAddress = (ULONG_PTR)Entry; RtlpTempBlocks[RtlpLDNumBlocks].Count = 0; RtlpTempBlocks[RtlpLDNumBlocks].Size = VirtualAllocBlock->CommitSize;
RtlpLDNumBlocks += 1;
RtlpPushPageDescriptor(RtlpLDCrtPage, EndPage - RtlpLDCrtPage + 1);
RtlpLDPreviousPage = 0;
} else if ( Context == CONTEXT_LOOKASIDE_BLOCK ) {
PBLOCK_DESCR PBlockDescr; LONG i; //
// Check whether we received a valid block
//
if (!RtlpGetMemoryFlag(&RtlpProcessMemoryMap, (ULONG_PTR)Entry)) {
DbgPrint("%p address isn't from the heap\n", Entry); } PBlockDescr = RtlpGetBlockInfo( &RtlpProcessMemoryMap, (ULONG_PTR)Entry );
if (!PBlockDescr) {
DbgPrint("Error finding block from lookaside %p\n", Entry);
return FALSE; }
//
// Find the block in the block descriptor
//
for (i = 0; i < PBlockDescr->Count; i++) {
if ((PBlockDescr->Blocks[i].BlockAddress <= (ULONG_PTR)Entry) && (PBlockDescr->Blocks[i].BlockAddress + PBlockDescr->Blocks[i].Size > (ULONG_PTR)Entry)) {
PBlockDescr->Blocks[i].Count = -10000;
//
// Remove the block from the busy list
//
RemoveEntryList(&PBlockDescr->Blocks[i].Entry);
return TRUE; } }
//
// A block from lookaside should be busy for the heap structures.
// If we didn't find the block in the block list, something went
// wrong. We make some noise here.
//
DbgPrint("Error, block %p from lookaside not found in allocated block list\n", Entry); } return TRUE; }
PHEAP_BLOCK RtlpGetHeapBlock ( IN ULONG_PTR Address )
/*++
Routine Description:
The function performs a lookup for the block descriptor for a given address. The address can point somewhere inside the block.
Arguments: Address - The lookup address.
Return Value:
Returns a pointer to the heap descriptor structure if found. This is not NULL if the given address belongs to any busy heap block.
--*/
{ PBLOCK_DESCR PBlockDescr; LONG i;
//
// Find the block descriptor for the given address
//
PBlockDescr = RtlpGetBlockInfo( &RtlpProcessMemoryMap, Address );
if ( (PBlockDescr != NULL) && (PBlockDescr->Heap != RtlpLeakHeapAddress)) {
//
// Search through the blocks
//
for (i = 0; i < PBlockDescr->Count; i++) {
if ((PBlockDescr->Blocks[i].BlockAddress <= Address) && (PBlockDescr->Blocks[i].BlockAddress + PBlockDescr->Blocks[i].Size > Address)) {
//
// Search again if the caller didn't pass a start address
//
if (PBlockDescr->Blocks[i].BlockAddress != Address) {
return RtlpGetHeapBlock(PBlockDescr->Blocks[i].BlockAddress); }
//
// we found a block here.
//
return &(PBlockDescr->Blocks[i]); } } }
return NULL; }
VOID RtlpDumpEntryHeader ( )
/*++
Routine Description:
Writes the table header Arguments: Return Value:
--*/
{ DbgPrint("Entry User Heap Size PrevSize Flags\n"); DbgPrint("------------------------------------------------------------\n"); }
VOID RtlpDumpEntryFlagDescription( IN ULONG Flags )
/*++
Routine Description:
The function writes a description string for the given block flag Arguments:
Flags - Block flags Return Value:
--*/
{ if (Flags & HEAP_ENTRY_BUSY) DbgPrint("busy "); else DbgPrint("free "); if (Flags & HEAP_ENTRY_EXTRA_PRESENT) DbgPrint("extra "); if (Flags & HEAP_ENTRY_FILL_PATTERN) DbgPrint("fill "); if (Flags & HEAP_ENTRY_VIRTUAL_ALLOC) DbgPrint("virtual "); if (Flags & HEAP_ENTRY_LAST_ENTRY) DbgPrint("last "); if (Flags & HEAP_ENTRY_SETTABLE_FLAGS) DbgPrint("user_flag "); }
VOID RtlpDumpEntryInfo( IN ULONG_PTR HeapAddress, IN PHEAP_ENTRY Entry )
/*++
Routine Description:
The function logs a heap block information Arguments:
HeapAddress - The heap that contains the entry to be displayied Entry - The block entry Return Value:
None.
--*/
{ DbgPrint("%p %p %p %8lx %8lx ", Entry, (Entry + 1), HeapAddress, Entry->Size << HEAP_GRANULARITY_SHIFT, Entry->PreviousSize << HEAP_GRANULARITY_SHIFT );
RtlpDumpEntryFlagDescription(Entry->Flags);
DbgPrint("\n"); }
BOOLEAN RtlpScanHeapAllocBlocks ( )
/*++
Routine Description:
The function does: - Scan all busy blocks and update the references to all other blocks - Build the list with leaked blocks - Reports the leaks
Arguments:
Return Value:
Return TRUE if succeeds.
--*/
{
PLIST_ENTRY Next;
//
// walk the busy list
//
Next = RtlpBusyList.Flink;
while (Next != &RtlpBusyList) { PHEAP_BLOCK Block = CONTAINING_RECORD(Next, HEAP_BLOCK, Entry); PULONG_PTR CrtAddress = (PULONG_PTR)(Block->BlockAddress + sizeof(HEAP_ENTRY)); //
// Move to the next block in the list
//
Next = Next->Flink;
//
// Iterate through block space and update
// the references for every block found here
//
while ((ULONG_PTR)CrtAddress < Block->BlockAddress + Block->Size) {
PHEAP_BLOCK pBlock = RtlpGetHeapBlock( *CrtAddress );
if (pBlock) {
//
// We found a block. we increment then the reference count
//
if (pBlock->Count == 0) { RemoveEntryList(&pBlock->Entry); InsertTailList(&RtlpBusyList, &pBlock->Entry); } pBlock->Count += 1;
if (pBlock->BlockAddress == RtlpBreakAtAddress) {
DbgBreakPoint(); } }
//
// Go to the next possible pointer
//
CrtAddress++; } }
//
// Now walk the leak list, and report leaks.
// Also any pointer found here will be dereferenced and added to
// the end of list.
//
Next = RtlpLeakList.Flink;
while (Next != &RtlpLeakList) { PHEAP_BLOCK Block = CONTAINING_RECORD(Next, HEAP_BLOCK, Entry); PBLOCK_DESCR PBlockDescr = RtlpGetBlockInfo( &RtlpProcessMemoryMap, Block->BlockAddress ); PULONG_PTR CrtAddress = (PULONG_PTR)(Block->BlockAddress + sizeof(HEAP_ENTRY));
if (PBlockDescr) {
//
// First time we need to display the header
//
if (RtlpLeaksCount == 0) {
RtlpDumpEntryHeader(); }
//
// Display the information for this block
//
RtlpDumpEntryInfo( PBlockDescr->Heap, (PHEAP_ENTRY)Block->BlockAddress);
RtlpLeaksCount += 1; } //
// Go to the next item from the leak list
//
Next = Next->Flink; }
return TRUE; }
BOOLEAN RtlpScanProcessVirtualMemory()
/*++
Routine Description:
This function scan the whole process virtual address space and lookup for possible references to busy blocks
Arguments:
Return Value:
Return TRUE if succeeds.
--*/
{ ULONG_PTR lpAddress = 0; MEMORY_BASIC_INFORMATION Buffer; NTSTATUS Status = STATUS_SUCCESS;
//
// Loop through virtual memory zones, we'll skip the heap space here
//
while ( NT_SUCCESS( Status ) ) { Status = ZwQueryVirtualMemory( NtCurrentProcess(), (PVOID)lpAddress, MemoryBasicInformation, &Buffer, sizeof(Buffer), NULL );
if (NT_SUCCESS( Status )) {
//
// If the page can be written, it might contain pointers to heap blocks
// We'll exclude at this point the heap address space. We scan the heaps
// later.
//
if ((Buffer.AllocationProtect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) && (Buffer.State & MEM_COMMIT) && !RtlpGetMemoryFlag(&RtlpProcessMemoryMap, (ULONG_PTR)lpAddress)) {
PULONG_PTR Pointers = (PULONG_PTR)lpAddress; ULONG_PTR i, Count;
//
// compute the number of possible pointers
//
Count = Buffer.RegionSize / sizeof(ULONG_PTR);
try {
//
// Loop through pages and check any possible pointer reference
//
for (i = 0; i < Count; i++) {
//
// Check whether we have a pointer to a busy heap block
//
PHEAP_BLOCK pBlock = RtlpGetHeapBlock(*Pointers);
if (pBlock) {
if (pBlock->BlockAddress == RtlpBreakAtAddress) {
DbgBreakPoint(); }
if (pBlock->Count == 0) { RemoveEntryList(&pBlock->Entry); InsertTailList(&RtlpBusyList, &pBlock->Entry); } pBlock->Count += 1; }
//
// Move to the next pointer
//
Pointers++; } } except( EXCEPTION_EXECUTE_HANDLER ) {
//
// Nothing more to do
//
} } //
// Move to the next VM range to query
//
lpAddress += Buffer.RegionSize; } } //
// Now update the references provided by the busy blocks
//
RtlpScanHeapAllocBlocks( );
return TRUE; }
VOID RtlDetectHeapLeaks ()
/*++
Routine Description:
This routine detects and display the leaks found in the current process NOTE: The caller must make sure no other thread can change some heap data while a tread is executing this one. In general this function is supposed to be called from LdrShutdownProcess. Arguments:
Return Value:
--*/
{
//
// Check if the global flag has the leak detection enabled
//
if (RtlpShutdownProcessFlags & (INSPECT_LEAKS | BREAK_ON_LEAKS)) { RtlpLeaksCount = 0;
//
// Create a temporary heap that will be used for any alocation
// of these functions.
//
RtlpLeakHeap = RtlCreateHeap(HEAP_NO_SERIALIZE | HEAP_GROWABLE, NULL, 0, 0, NULL, NULL);
if (RtlpLeakHeap) {
PPEB ProcessPeb = NtCurrentPeb();
HeapDebugPrint( ("Inspecting leaks at process shutdown ...\n") );
RtlpInitializeLeakDetection();
//
// The last heap from the heap list is our temporary heap
//
RtlpLeakHeapAddress = (ULONG_PTR)ProcessPeb->ProcessHeaps[ ProcessPeb->NumberOfHeaps - 1 ];
//
// Scan all process heaps, build the memory map and
// the busy block list
//
RtlpReadProcessHeaps( RtlpRegisterHeapBlocks ); //
// Scan the process virtual memory and the busy blocks
// At the end build the list with leaked blocks and report them
//
RtlpScanProcessVirtualMemory();
//
// Destroy the temporary heap
//
RtlDestroyHeap(RtlpLeakHeap);
RtlpLeakHeap = NULL;
//
// Report the final statement about the process leaks
//
if (RtlpLeaksCount) { HeapDebugPrint(("%ld leaks detected.\n", RtlpLeaksCount));
if (RtlpShutdownProcessFlags & BREAK_ON_LEAKS) {
DbgBreakPoint(); } } else {
HeapDebugPrint( ("No leaks detected.\n")); } } } }
|