|
|
//
// Copyright (c) 2001 Microsoft Corporation
//
// Module Name
//
// gc.c
//
// Abstract
//
// Implementation of APIs used for garbage collection in UMDH. These
// APIs are declared in the header file gc.h
//
// Garbage Collection: The automatic reclamation of heap-allocated
// storage after its last use by a program.
//
// Garbage collection is done in two steps-
// 1. Identify the garbage
// 2. Reclaim the garbage
//
// Since UMDH is not an in-process tool, only part 1 is accomplished
// in this implementation.
//
// Garbage Collection Algorithm:
//
// It uses Mark-Sweep (not exactly) to identify live objects from
// garbage.
//
// 1. Grovel through the entire process virtual memory and identify
// the live objects by incrementing the reference counts of the
// heap objects.
//
// 2. Create a list for those heap objects (garbage) whose reference
// count is zero.
//
// 3. Identify the heap objects (not in garbage) referenced by these
// objects from the garbage and decrement the count by one. If the
// reference count drops to zero, add the heap object to the list
// of objects in garbage.
//
// 4. Continue till all the objects in the garbage are traversed and
// reference counts are incremented/decremented accordingly.
//
// 5. Dump the list of objects in garbage.
//
// To improve the number of leaks detected by this algorithm, reference
// counts of the heap objects are not incremented, if the object
// reference is from invalid stack regions (those regions of the stack
// which are read/write but above the stack pointer) when grovelling
// through the virtual memory in step one.
//
//
// Authors
//
// Narayana Batchu (nbatchu) 21-Jun-01
//
// Revision History
//
// NBatchu 21-Jun-01 Initial version
// NBatchu 24-Sep-01 Performance optimizations
//
//
// Wish List
//
//
// [-] Producing stack traces along with the leak table
//
// [-] Sorting the leaks by the stack traces (TraceIndex)
//
// [-] Adding the logic to detect circular reference counting. When blocks
// are circularly referenced, then this algorithm would not be able to
// detect leaks
//
// [-] Adding code for ia64, to filter out invalid stack regions. As of now
// this is implemented for x86 machines only
//
//
// Bugs
//
// [-] Partial copy error when reading process virtual memory - as of now
// we are ignoring those errors
//
#include <ntos.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <heap.h>
#include <heappriv.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <tlhelp32.h>
#include "miscellaneous.h"
#include "gc.h"
#define BLOCK_CAPACITY 512
#define HEAP_CAPACITY 8
#define MAX_INDEX 0xffffffff
#define MAX_THREADS 128
#define MAX_HEAP_BLOCK_SIZE 4096
#define MAX_VIRTUAL_BLOCK_SIZE (64*1024)
//
// Handle to the process
//
HANDLE g_hProcess;
//
// InitializeHeapBlock
//
// Initializes the HEAP_BLOCK structure
//
// Arguments
//
// Block Pointer to a HEAP_BLOCK to be initialized
//
// ReturnValue
//
VOID InitializeHeapBlock( PHEAP_BLOCK Block ) { if (NULL == Block) { return; }
Block->BlockAddress = 0; Block->BlockSize = 0; Block->RefCount = 0; Block->TraceIndex = 0; }
//
// SetHeapBlock
//
// Sets the fields of HEAP_BLOCK structure
//
// Arguments
//
// Block Pointer to a HEAP_BLOCK whose fields to be set
//
// ReturnValue
//
VOID SetHeapBlock( PHEAP_BLOCK Block, ULONG_PTR BlockAddress, ULONG BlockSize, USHORT TraceIndex ) { if (NULL == Block) { return; }
Block->BlockAddress = BlockAddress; Block->BlockSize = BlockSize; Block->RefCount = 0; Block->TraceIndex = TraceIndex; }
//
// InitializeBlockList
//
// Initializes the BLOCK_LIST structure
//
// Arguments
//
// BlockList Pointer to a BLOCK_LIST to be initialized
//
// ReturnValue
//
BOOL InitializeBlockList( PBLOCK_LIST BlockList ) { BOOL fSuccess = TRUE;
if (NULL == BlockList) { goto Exit; }
BlockList->HeapAddress = 0; BlockList->BlockCount = 0; BlockList->Capacity = BLOCK_CAPACITY; BlockList->ListSorted = TRUE;
BlockList->Blocks = (PHEAP_BLOCK)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BlockList->Capacity * sizeof(HEAP_BLOCK));
if (NULL == BlockList->Blocks) {
BlockList->Capacity = 0;
Error (__FILE__, __LINE__, "HeapAlloc failed while allocating more memory");
fSuccess = FALSE; }
Exit: return fSuccess; }
//
// FreeBlockList
//
// Frees the memory allocted for the Blocks field (if any)
// while initializing this BLOCK_LIST structure.
//
// Arguments
//
// BlockList Pointer to a BLOCK_LIST
//
// ReturnValue
//
VOID FreeBlockList( PBLOCK_LIST BlockList ) {
if (NULL == BlockList) { return; }
BlockList->HeapAddress = 0; BlockList->BlockCount = 0; BlockList->Capacity = 0; BlockList->ListSorted = TRUE;
if (NULL != BlockList->Blocks) {
HeapFree(GetProcessHeap(), 0, BlockList->Blocks); BlockList->Blocks = NULL; } }
//
// IntializeHeapList
//
// Initializes HEAP_LIST structure
//
// Arguments
//
// HeapList Pointer to a HEAP_LIST
//
// ReturnValue
//
BOOL InitializeHeapList( PHEAP_LIST HeapList ) { ULONG Index; BOOL fSuccess = TRUE; if (NULL == HeapList) { goto Exit; } HeapList->Capacity = HEAP_CAPACITY; HeapList->HeapCount = 0;
HeapList->Heaps = (PBLOCK_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(BLOCK_LIST) * HeapList->Capacity);
if (NULL == HeapList->Heaps) {
HeapList->Capacity = 0;
Error (__FILE__, __LINE__, "HeapAlloc failed while allocating more memory");
fSuccess = FALSE; }
Exit: return fSuccess; }
//
// FreeHeapList
//
// Frees the memory allocted for the Heaps field (if any)
// while initializing this HEAP_LIST structure.
//
// Arguments
//
// BlockList Pointer to a HEAP_LIST
//
// ReturnValue
//
VOID FreeHeapList( PHEAP_LIST HeapList ) { ULONG Index;
if (NULL == HeapList) { return; }
HeapList->Capacity = 0; HeapList->HeapCount = 0;
if (NULL != HeapList->Heaps) {
for (Index=0; Index<HeapList->HeapCount; Index++) {
FreeBlockList(&HeapList->Heaps[Index]); }
HeapFree(GetProcessHeap(), 0, HeapList->Heaps); HeapList->Heaps = NULL; } }
//
// InitializeAddressList
//
// Initializes a ADDRESS_LIST object
//
// Arguments
//
// AddressList Pointer to ADDRESS_LIST structure
//
// ReturnValue
//
VOID InitializeAddressList( PADDRESS_LIST AddressList ) { if (NULL == AddressList) { return; }
AddressList->Address = 0; InitializeListHead(&(AddressList->Next)); }
//
// FreeAddressList
//
// Frees the memory allocated for the linked list
//
// Arguments
//
// AddressList Pointer to ADDRESS_LIST to be freed
//
// ReturnValue
//
VOID FreeAddressList( PADDRESS_LIST AddressList ) { PLIST_ENTRY NextEntry; PLIST_ENTRY Entry; PADDRESS_LIST List;
if (NULL == AddressList) { return; }
//
// Walk through the list and free up the memory
//
NextEntry = &AddressList->Next; while (!IsListEmpty(NextEntry)) {
Entry = RemoveHeadList(NextEntry);
List = CONTAINING_RECORD(Entry, ADDRESS_LIST, Next);
HeapFree(GetProcessHeap(), 0, List); } }
//
// IncreaseBlockListCapacity
//
// Increases the storing capacity for the BLOCK_LIST
// structure. Every time this function is called the storing
// capacity doubles. There is a trade off between the number
// of times HeapReAlloc is called and the amount of memory
// is allocated.
//
// Arguments
//
// BlockList Pointer to a BLOCK_LIST object
//
// ReturnValue
//
// BOOL Returns TRUE if successful in increasing the
// capacity of BlockList.
//
BOOL IncreaseBlockListCapacity( PBLOCK_LIST BlockList ) { BOOL fSuccess = FALSE; ULONG NewCapacity; PVOID NewBlockList;
if (NULL == BlockList) { goto Exit; }
NewCapacity = BlockList->Capacity * 2;
if (0 == NewCapacity) {
fSuccess = InitializeBlockList(BlockList); goto Exit; }
NewBlockList = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BlockList->Blocks, NewCapacity * sizeof(HEAP_BLOCK));
if (NULL != NewBlockList) {
BlockList->Blocks = (PHEAP_BLOCK)NewBlockList; BlockList->Capacity = NewCapacity; fSuccess = TRUE; } else {
Error (__FILE__, __LINE__, "HeapReAlloc failed while allocating more memory"); }
Exit: return fSuccess; }
//
// IncreaseHeapListCapacity
//
// Increases the storing capacity for the HEAP_LIST
// structure. Every time this function is called the storing
// capacity doubles. There is a trade off between the number
// of times HeapReAlloc is called and the amount of memory
// is allocated.
//
// Arguments
//
// BlockList Pointer to a HEAP_LIST object
//
// ReturnValue
//
// BOOL Returns TRUE if successful in increasing the
// capacity of HeapList.
//
BOOL IncreaseHeapListCapacity( PHEAP_LIST HeapList ) { BOOL fSuccess = FALSE; ULONG NewCapacity; PVOID NewHeapList;
if (NULL == HeapList) { goto Exit; }
NewCapacity = HeapList->Capacity * 2;
if (0 == NewCapacity) {
fSuccess = InitializeHeapList(HeapList); goto Exit; }
NewHeapList = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, HeapList->Heaps, NewCapacity * sizeof(BLOCK_LIST));
if (NULL != NewHeapList) {
HeapList->Heaps = (PBLOCK_LIST)NewHeapList; HeapList->Capacity = NewCapacity; fSuccess = TRUE; } else {
Error(__FILE__, __LINE__, "HeapReAlloc failed while allocating more memory"); }
Exit: return fSuccess; }
//
// InsertHeapBlock
//
// Inserts HEAP_BLOCK object into BLOCK_LIST. BLOCK_LIST is
// an array of HEAP_BLOCKs belonging to a particular heap.
//
// Arguments
//
// BlockList Pointer to BLOCK_LIST. HEAP_BLOCK is inserted
// into this list.
//
// Block Pointer to HEAP_BLOCK to be inserted in.
//
// ReturnValue
//
// ULONG Returns the Index at which HEAP_BLOCK is inserted
// in BLOCK_LIST
//
ULONG InsertHeapBlock( PBLOCK_LIST BlockList, PHEAP_BLOCK Block ) { ULONG Index = MAX_INDEX; BOOL Result;
if (NULL == BlockList || NULL == Block) { goto Exit; }
Index = BlockList->BlockCount;
if (Index >= BlockList->Capacity) {
//
// Try to increase block list capacity.
//
if (!IncreaseBlockListCapacity(BlockList)) {
goto Exit; } }
BlockList->Blocks[Index].BlockAddress = Block->BlockAddress; BlockList->Blocks[Index].BlockSize = Block->BlockSize; BlockList->Blocks[Index].RefCount = Block->RefCount; BlockList->Blocks[Index].TraceIndex = Block->TraceIndex;
BlockList->BlockCount += 1; BlockList->ListSorted = FALSE;
Exit: return Index; }
//
// InsertBlockList
//
// Inserts BLOCK_LIST object into HEAP_LIST. HEAP_LIST is
// an array of BLOCK_LISTs belonging to a particular process.
// And BLOCK_LIST is an array of HEAP_BLOCKs belonging to a
// particular heap.
//
// Arguments
//
// BlockList Pointer to BLOCK_LIST. HEAP_BLOCK is inserted
// into this list.
//
// Block Pointer to HEAP_BLOCK to be inserted in.
//
// ReturnValue
//
// ULONG Returns the Index at which HEAP_BLOCK is inserted
// in BLOCK_LIST
//
ULONG InsertBlockList( PHEAP_LIST HeapList, PBLOCK_LIST BlockList ) { ULONG I, Index = MAX_INDEX; PBLOCK_LIST NewBlockList;
if (NULL == HeapList || NULL == BlockList) { goto Exit; }
if (0 == BlockList->BlockCount) { goto Exit; }
Index = HeapList->HeapCount;
if (Index >= HeapList->Capacity) {
//
// Increase the heap list capacity since we hit the limit.
//
if (!IncreaseHeapListCapacity(HeapList)) {
goto Exit; } }
HeapList->Heaps[Index].Blocks = BlockList->Blocks; NewBlockList = &HeapList->Heaps[Index];
//
// Copy the values stored in BlockList to NewBlockList.
//
NewBlockList->BlockCount = BlockList->BlockCount; NewBlockList->Capacity = BlockList->Capacity; NewBlockList->HeapAddress = BlockList->HeapAddress; NewBlockList->ListSorted = BlockList->ListSorted;
//
// Increment the HeapCount
//
HeapList->HeapCount += 1;
Exit: return Index; }
//
// GetThreadHandles
//
// Enumerates all the threads in the system and filters only the
// threads in the process we are concerned
//
// Arguments
//
// ProcessId Process ID
//
// ThreadHandles Array of Handles, which receive the handles to
// the enumerated threads
//
// Count Array count
//
// ReturnValue
//
// DWORD Returns the number of thread handles opened
//
DWORD GetThreadHandles( DWORD ProcessId, LPHANDLE ThreadHandles, ULONG Count ) {
HANDLE ThreadSnap = NULL; BOOL Result = FALSE; THREADENTRY32 ThreadEntry = {0}; ULONG I, Index = 0;
// SilviuC: These APIs xxxtoolhelpxxx are crappy. Keep them for now but
// you should get yourself familiarized with NT apis that do the same. For
// instance take a look in sdktools\systrack where there is code that gets
// stack information for each thread.
//
// Take a snapshot of all the threads in the system
//
ThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (NULL == ThreadSnap) {
Error (__FILE__, __LINE__, "CreateToolhelp32Snapshot failed with error : %ld\n", GetLastError());
goto Exit; }
//
// Fill in the size for ThreadEntry before using it
//
ThreadEntry.dwSize = sizeof(THREADENTRY32);
//
// Walk through snap shot of threads and look for the threads
// whose process ids match the process id of the process we
// are looking for.
//
Result = Thread32First(ThreadSnap, &ThreadEntry);
while (Result) {
if (ThreadEntry.th32OwnerProcessID == ProcessId) {
HANDLE ThreadHandle = OpenThread(THREAD_GET_CONTEXT, FALSE, ThreadEntry.th32ThreadID);
if (NULL == ThreadHandle) {
Error (__FILE__, __LINE__, "OpenThread failed with error : %ld\n", GetLastError());
} else {
if (NULL != ThreadHandles && Index < Count) {
ThreadHandles[Index] = ThreadHandle; }
Index += 1; }
}
Result = Thread32Next(ThreadSnap, &ThreadEntry); }
Exit:
//
// Clean up the snapshot object
//
if (NULL != ThreadSnap) {
CloseHandle (ThreadSnap); }
return Index; }
//
// GetThreadContexts
//
// Gets the thread contexts of all the threads in the process
//
// Arguments
//
// ThreadContexts Array of CONTEXT structures to store thread
// stack/context information
//
// ThreadHandles Array of thread handles
//
// Count Array count
//
// ReturnValue
//
// BOOL Returns true if successful
//
BOOL GetThreadContexts( PCONTEXT ThreadContexts, LPHANDLE ThreadHandles, ULONG Count ) { ULONG Index; BOOL Result;
for (Index = 0; Index < Count; Index += 1) { ZeroMemory(&ThreadContexts[Index], sizeof(CONTEXT)); ThreadContexts[Index].ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
Result = GetThreadContext (ThreadHandles[Index], &ThreadContexts[Index]);
if (FALSE == Result) {
Error (__FILE__, __LINE__, "GetThreadContext Failed with error : %ld\n", GetLastError()); } }
return TRUE; }
//
// StackFilteredAddress
//
// Each thread in the process has its own stack and each stack
// has a read/write region that is not valid (this region is
// above the stack pointer). This function filters out this
// region by incrementing the start address of the block to
// the end of stack pointer, so that we dont search those
// regions of the stack which dont contain valid data.
//
// As af now, this function is implemented for X86 machines only.
// For IA64 machines, the register names (in the CONTEXT structure)
// are different than X86 machines and different header files need
// to be added to make it compile and work.
//
// Arguments
//
// Address Address to the block
//
// Size Size of the block pointed to 'Address'
//
// ThreadContexts Array to CONTEXTs of all the threads in
// the process
//
// Count Array count
//
// ReturnValue
//
// ULONG_PTR Returns new address to the end of the
// valid stack region
//
ULONG_PTR StackFilteredAddress( ULONG_PTR Address, SIZE_T Size, PCONTEXT ThreadContexts, ULONG Count ) { ULONG Index; ULONG_PTR FilteredAddress = Address;
//
// SilviuC: It is easy to get the same kind of stuff for IA64. If I am not
// mistaken the field is called Sp.
//
#ifdef X86
for (Index = 0; Index < Count; Index += 1) {
if (ThreadContexts[Index].Esp >= Address && ThreadContexts[Index].Esp <= Address + Size) {
FilteredAddress = ThreadContexts[Index].Esp; break; } }
#endif
return FilteredAddress; }
//
// SortByBlockAddress
//
// Sorts HEAP_BLOCKs belonging to a particular BLOCK_LIST
// by comparing the BlockAddresses.
//
// Compare function required by qsort (uses quick sort to sort
// the elements in the array).
//
// More info about the arguments and the return values could be
// found in MSDN.
//
int __cdecl SortByBlockAddress ( const PHEAP_BLOCK Block1, const PHEAP_BLOCK Block2 ) { int iCompare;
if (Block1->BlockAddress > Block2->BlockAddress) {
iCompare = +1; } else if (Block1->BlockAddress < Block2->BlockAddress) {
iCompare = -1; } else {
iCompare = 0; }
return iCompare; }
int __cdecl SortByTraceIndex ( const PHEAP_BLOCK Block1, const PHEAP_BLOCK Block2 ) { int iCompare;
//
// Sort such that items with identical TraceIndex are adjacent.
// (That this results in ascending order is irrelevant).
//
if (Block1->TraceIndex > Block2->TraceIndex) {
iCompare = +1; } else if (Block1->TraceIndex < Block2->TraceIndex) {
iCompare = -1; } else {
iCompare = 0; }
if (0 == iCompare) { //
// For two items with identical TraceIndex, sort into ascending
// order by BytesAllocated.
//
if (Block1->BlockSize > Block2->BlockSize) {
iCompare = 1; } else if (Block1->BlockSize < Block2->BlockSize) {
iCompare = -1; } else {
iCompare = 0; } }
return iCompare; }
//
// SortHeaps
//
// Sorts all the heaps in the HEAP_LIST.
// Each heap is sorted by increasing value of HEAP_BLOCK
// addresses. The top most entry for each heap would be
// having the min address value
//
// Arguments
//
// HeapList Pointer to HEAP_LIST
//
// Return Value
//
VOID SortHeaps( PHEAP_LIST HeapList, int (__cdecl *compare )(const void *elem1, const void *elem2 ) ) { ULONG HeapCount; ULONG Index;
if (NULL == HeapList) { return; }
HeapCount = HeapList->HeapCount;
for (Index = 0; Index < HeapCount; Index += 1) {
//
// Sort the BLOCK_LIST only if it contains heap objects
//
if (0 != HeapList->Heaps[Index].BlockCount) {
qsort (HeapList->Heaps[Index].Blocks, HeapList->Heaps[Index].BlockCount, sizeof(HEAP_BLOCK), compare); }
HeapList->Heaps[Index].ListSorted = TRUE; } }
//
// GetHeapBlock
//
// Finds a HEAP_BLOCK whose range contains the the address
// pointed to by Address
//
// Arguments
//
// Address Address as ULONG_PTR
//
// HeapList Pointer to HEAP_LIST to be searched.
//
// ReturnValue
//
// PHEAP_BLOCK Returns the pointer to the HEAP_BLOCK that
// contains the address.
//
PHEAP_BLOCK GetHeapBlock ( ULONG_PTR Address, PHEAP_LIST HeapList ) { PHEAP_BLOCK Block = NULL; ULONG I,J; ULONG Start, Mid, End; PBLOCK_LIST BlockList;
//
// Since most of the memory is null (zero), this check would
// improve the performance
//
if (0 == Address || NULL == HeapList || 0 == HeapList->HeapCount) {
goto Exit; }
for (I = 0; I < HeapList->HeapCount; I += 1) {
//
// Ignore if the heap contains no objects
//
if (0 == HeapList->Heaps[I].BlockCount) {
continue; } //
// Binary search the address in the sorted list of heap blocks for
// the current heap.
//
Start = 0; End = HeapList->Heaps[I].BlockCount - 1; BlockList = &HeapList->Heaps[I];
while (Start <= End) {
Mid = (Start + End)/2;
if (Address < BlockList->Blocks[Mid].BlockAddress) {
End = Mid - 1; } else if (Address >= BlockList->Blocks[Mid].BlockAddress + BlockList->Blocks[Mid].BlockSize) {
Start = Mid + 1; } else {
Block = &BlockList->Blocks[Mid]; break; }
if (Mid == Start || Mid == End) {
break; }
}
if (NULL != Block) { break; } }
Exit:
return Block; }
//
// InsertAddress
//
// Inserts a node in the linked list. The new node has the
// Address stored. This node is inserted at the end of the
// linked list.
//
// Arguments
//
// Address Address of a block in the heap
//
// List Pointer to a ADDRESS_LIST
//
// ReturnValue
//
VOID InsertAddress( ULONG_PTR Address, PADDRESS_LIST List ) { PADDRESS_LIST NewList; NewList = (PADDRESS_LIST) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof (ADDRESS_LIST)); if (NULL == NewList) {
Error (__FILE__, __LINE__, "HeapAlloc failed to allocate memory");
return; }
NewList->Address = Address; InsertTailList(&(List->Next), &(NewList->Next)); }
//
// DumpLeakList
//
// Dumps the leak list to a file or console. Parses through
// each of the HEAP_BLOCK and dumps those blocks whose RefCount
// is 0 (zero).
//
// Arguments
//
// File Output file
//
// HeapList Pointer to a HEAP_LIST
//
// ReturnValue
//
VOID DumpLeakList( FILE * File, PHEAP_LIST HeapList ) {
ULONG I,J;
ULONG Count = 1;
USHORT RefTraceIndex = 0;
ULONG TotalBytes = 0;
PHEAP_BLOCK HeapBlock;
SortHeaps(HeapList, SortByTraceIndex);
//
// Now walk the heap list, and report leaks.
//
fprintf( File, "\n\n*- - - - - - - - - - Leaks detected - - - - - - - - - -\n\n" );
for (I = 0; I < HeapList->HeapCount; I += 1) {
for (J = 0; J < HeapList->Heaps[I].BlockCount; J += 1) {
HeapBlock = &(HeapList->Heaps[I].Blocks[J]);
//
// Merge the leaks whose trace index is same (i.e. whose
// allocation stack trace is same)
//
if (RefTraceIndex == HeapBlock->TraceIndex && 0 == HeapBlock->RefCount) {
Count += 1;
TotalBytes += HeapBlock->BlockSize; }
//
// Display them if
// 1. They are from different stack traces and there are leaks
// OR
// 2. This is the last Block in the list and there are leaks.
//
if ((RefTraceIndex != HeapBlock->TraceIndex) || ((I+1) == HeapList->HeapCount && (J+1) == HeapList->Heaps[I].BlockCount)) {
if (0 != RefTraceIndex && 0 != TotalBytes) {
fprintf( File, "0x%x bytes leaked by: BackTrace%05d (in 0x%04x allocations)\n", TotalBytes, RefTraceIndex, Count ); } //
// Update trace index, count and total bytes
//
RefTraceIndex = HeapBlock->TraceIndex;
Count = (0 == HeapBlock->RefCount) ? 1 : 0;
TotalBytes = (0 == HeapBlock->RefCount) ? HeapList->Heaps[I].Blocks[J].BlockSize : 0; } } }
fprintf( File, "\n*- - - - - - - - - - End of Leaks - - - - - - - - - -\n\n" );
return; }
//
// ScanHeapFreeBlocks
//
// Scans the free list and updates the references to any of the
// busy blocks. When the refernce count of the busy blocks drops
// to zero, it is appended to the end of the free list
//
// Arguments
//
// HeapList Pointer to HEAP_LIST
//
// FreeList Pointer to ADDRESS_LIST that contains addresses to
// free heap blocks
//
// ReturnValue
//
// BOOL Returns true if successful
//
BOOL ScanHeapFreeBlocks( PHEAP_LIST HeapList, PADDRESS_LIST FreeList ) { BOOL Result; ULONG Count, i; PULONG_PTR Pointer; ULONG_PTR FinalAddress; PHEAP_BLOCK CurrentBlock; PVOID HeapBlock; ULONG HeapBlockSize = 0; BOOL Success = TRUE; PLIST_ENTRY FirstEntry; PLIST_ENTRY NextEntry; PADDRESS_LIST AddressList;
//
// Allocate a chunk of memory for reading heap objects
//
HeapBlock = (PVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_HEAP_BLOCK_SIZE);
if (NULL == HeapBlock) {
Error (__FILE__, __LINE__, "HeapAlloc failed to allocate memory");
Success = FALSE; goto Exit; }
HeapBlockSize = MAX_HEAP_BLOCK_SIZE;
//
// Walk the free list by deleting the entries read
//
FirstEntry = &(FreeList->Next); while (!IsListEmpty(FirstEntry)) { NextEntry = RemoveHeadList(FirstEntry);
AddressList = CONTAINING_RECORD(NextEntry, ADDRESS_LIST, Next);
CurrentBlock = GetHeapBlock(AddressList->Address, HeapList);
assert(NULL != CurrentBlock);
if (NULL == CurrentBlock) {
Error (__FILE__, __LINE__, "GetHeapBlock returned NULL. May be because of reading stale memory");
continue; }
if (HeapBlockSize < CurrentBlock->BlockSize) {
if (NULL != HeapBlock) {
HeapFree(GetProcessHeap(), 0, HeapBlock); }
HeapBlock = (PVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, CurrentBlock->BlockSize);
if (NULL == HeapBlock) {
Error (__FILE__, __LINE__, "HeapAlloc failed to allocate memory");
Success = FALSE; goto Exit; }
HeapBlockSize = CurrentBlock->BlockSize; }
//
// Read the contents of the freed heap block
// from the target process.
//
Result = UmdhReadAtVa(__FILE__, __LINE__, g_hProcess, (PVOID)CurrentBlock->BlockAddress, HeapBlock, CurrentBlock->BlockSize);
if (Result) {
FinalAddress = (ULONG_PTR)HeapBlock+CurrentBlock->BlockSize;
Pointer = (PULONG_PTR) HeapBlock;
while ((ULONG_PTR)Pointer < FinalAddress) {
//
// Check whether we have a pointer to a
// busy heap block
//
PHEAP_BLOCK Block = GetHeapBlock(*Pointer,HeapList);
if (NULL != Block) {
//
// We found a block. we decrement the reference
// count
//
if (0 == Block->RefCount) {
//
// This should never happen!!
//
Error (__FILE__, __LINE__, "Something wrong! Should not get a block whose " "RefCount is already 0 @ %p", Block->BlockAddress); }
else if (1 == Block->RefCount) {
//
// Queue the newly found free block at the end of
// the list of freed heap blocks. The block has become
// eligible for this because `HeapBlock' contained the
// last remaining reference to `Block'.
//
InsertAddress(Block->BlockAddress, FreeList); Block->RefCount = 0; }
else {
Block->RefCount -= 1; }
}
//
// Move to the next pointer
//
Pointer += 1; } } }
Exit:
//
// Free the memory allocated at HeapBlock
//
if (NULL != HeapBlock) {
HeapFree (GetProcessHeap(), 0, HeapBlock); HeapBlock = NULL; }
return Success; }
//
// ScanProcessVirtualMemory
//
// Scans the virtual memory and updates the RefCount of the heap
// blocks. This also takes care excluding the invalid stack
// regions that might contain valid pointers.
//
// Arguments
//
// Pid Process ID
//
// FreeList Pointer to a ADDRESS_LIST that holds the address of
// all free heap blocks
//
// HeapList Pointer to HEAP_LIST
//
// ReturnValue
//
// BOOL Returns true if successful in scanning through the
// virtual memory
BOOL ScanProcessVirtualMemory( ULONG Pid, PADDRESS_LIST FreeList, PHEAP_LIST HeapList ) {
ULONG_PTR Address = 0; MEMORY_BASIC_INFORMATION Buffer; PVOID VirtualBlock; ULONG VirtualBlockSize; SYSTEM_INFO SystemInfo; LPVOID MinAddress; LPVOID MaxAddress; LPHANDLE ThreadHandles; PCONTEXT ThreadContexts; ULONG ThreadCount; ULONG Index; SIZE_T dwBytesRead = 1; BOOL Success = TRUE;
//
// Enumerate all the threads in the process and get their
// stack information
//
//
// Get the count of threads in the process
//
ThreadCount = GetThreadHandles (Pid, NULL, 0);
//
// Allocate memory for ThreadHandles
//
ThreadHandles = (LPHANDLE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ThreadCount * sizeof(HANDLE));
if (NULL == ThreadHandles) {
Error (__FILE__, __LINE__, "HeapAlloc failed for ThreadHandles");
ThreadCount = 0; }
//
// Get the handles to the threads in the process
//
GetThreadHandles(Pid, ThreadHandles, ThreadCount);
//
// Allocate memory for ThreadContexts
//
ThreadContexts = (PCONTEXT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ThreadCount * sizeof(CONTEXT));
if (NULL == ThreadContexts) {
Error (__FILE__, __LINE__, "HeapAlloc failed for ThreadContexts");
ThreadCount = 0; }
GetThreadContexts (ThreadContexts, ThreadHandles, ThreadCount);
//
// We need to know maximum and minimum address space that we can
// grovel. SYSTEM_INFO has this information.
//
GetSystemInfo(&SystemInfo);
MinAddress = SystemInfo.lpMinimumApplicationAddress; MaxAddress = SystemInfo.lpMaximumApplicationAddress;
//
// Loop through virtual memory zones
//
Address = (ULONG_PTR)MinAddress;
//
// Allocate chunk of memory for virtual block
//
VirtualBlock = (PVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_VIRTUAL_BLOCK_SIZE);
if (NULL == VirtualBlock) {
Error (__FILE__, __LINE__, "HeapAlloc failed to allocate memory");
Success = FALSE; goto Exit; }
VirtualBlockSize = MAX_VIRTUAL_BLOCK_SIZE;
//
// dwBytesRead equals 1 when we enter the loop for the first time due
// to previous initialization at the start of the function.
//
while (0 != dwBytesRead && Address < (ULONG_PTR)MaxAddress) { dwBytesRead = VirtualQueryEx (g_hProcess, (PVOID)Address, &Buffer, sizeof(Buffer));
if (0 != dwBytesRead) {
DWORD dwFlags = (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY);
//
// If the page can be written, it might contain pointers
// to heap blocks.
//
if ((Buffer.AllocationProtect & dwFlags) && (Buffer.State & MEM_COMMIT)) {
PULONG_PTR Pointer; ULONG_PTR FinalAddress; ULONG_PTR FilteredAddress; SIZE_T NewRegionSize; BOOL Result; int j; SIZE_T BytesRead = 0;
FilteredAddress = StackFilteredAddress(Address, Buffer.RegionSize, ThreadContexts, ThreadCount); NewRegionSize = Buffer.RegionSize - (SIZE_T)( (ULONG_PTR)FilteredAddress - (ULONG_PTR)Address);
if (VirtualBlockSize < NewRegionSize) {
if (NULL != VirtualBlock) {
HeapFree(GetProcessHeap(), 0, VirtualBlock); VirtualBlock = NULL; VirtualBlockSize = 0; }
VirtualBlock = (PVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, NewRegionSize);
if (NULL == VirtualBlock) {
Error ( __FILE__, __LINE__, "HeapAlloc failed to allocate memory" );
Success = FALSE; goto Exit; }
VirtualBlockSize = (ULONG)NewRegionSize; } Result = ReadProcessMemory(g_hProcess, (PVOID)FilteredAddress, VirtualBlock, NewRegionSize, &BytesRead);
assert(NewRegionSize == BytesRead);
FinalAddress = (ULONG_PTR)VirtualBlock + BytesRead; Pointer = (PULONG_PTR) VirtualBlock;
//
// Loop through pages and check any possible
// pointer reference
//
while ((ULONG_PTR)Pointer < FinalAddress) {
PHEAP_BLOCK Block; //
// Check whether we have a pointer to a
// busy heap block
//
Block = GetHeapBlock(*Pointer,HeapList);
if (NULL != Block) {
Block->RefCount += 1; }
//
// Move to the next pointer
//
Pointer += 1; }
} //
// Move to the next VM range to query
//
Address += Buffer.RegionSize; } }
//
// Create a linked list of free heap blocks
//
{ ULONG i, j;
for (i=0; i<HeapList->HeapCount; i++)
for (j=0; j<HeapList->Heaps[i].BlockCount; j++)
if (0 == HeapList->Heaps[i].Blocks[j].RefCount)
InsertAddress(HeapList->Heaps[i].Blocks[j].BlockAddress, FreeList); } Exit:
//
// Close the ThreadHandles opened
//
for (Index = 0; Index < ThreadCount; Index += 1) {
CloseHandle (ThreadHandles[Index]); ThreadHandles[Index] = NULL; }
//
// Cleanup the memory allocated for ThreadHandles
//
if (NULL != ThreadHandles) {
HeapFree(GetProcessHeap(), 0, ThreadHandles); ThreadHandles = NULL; }
//
// Cleanup the memory allocated for ThreadContexts
//
if (NULL != ThreadContexts) {
HeapFree(GetProcessHeap(), 0, ThreadContexts); ThreadContexts = NULL; }
//
// Free up the memory allocated for VirtualBlock
//
if (NULL != VirtualBlock ) { HeapFree (GetProcessHeap(), 0, VirtualBlock); VirtualBlock = NULL; }
return Success; }
VOID DetectLeaks( PHEAP_LIST HeapList, ULONG Pid, FILE * OutFile ) { ADDRESS_LIST FreeList;
//
// Initialize the linked list
//
InitializeAddressList(&FreeList);
//
// Get a handle to the process
//
g_hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SUSPEND_RESUME, FALSE, Pid);
if (NULL == g_hProcess) {
Error (__FILE__, __LINE__, "OpenProcess (%u) failed with error %u", Pid, GetLastError() );
goto Exit; }
//
// Sort Heaps
//
SortHeaps(HeapList, SortByBlockAddress);
//
// Scan through virtual memory zones
//
ScanProcessVirtualMemory(Pid, &FreeList, HeapList);
//
// Update references provided by the free blocks
//
ScanHeapFreeBlocks(HeapList, &FreeList);
//
// Dump the list of leaked blocks
//
DumpLeakList(OutFile, HeapList);
Exit:
//
// Close the process handle
//
if (NULL != g_hProcess) {
CloseHandle(g_hProcess);
g_hProcess = NULL; }
//
// Free the memory associated with FreeList
//
FreeAddressList(&FreeList); }
|