/*++ 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")); } } } }