/*++ Copyright (c) 2000 Microsoft Corporation Module Name: heapleak.c Abstract: WinDbg Extension Api Author: Adrian Marinescu (adrmarin) 04/17/2000 Environment: User Mode. Revision History: --*/ #include "precomp.h" #include "heap.h" #pragma hdrstop ULONG64 AddressToFind; ULONG64 HeapAddressFind; ULONG64 SegmentAddressFind; ULONG64 HeapEntryFind; ULONG64 HeapEntryFindSize; BOOLEAN Lookaside; BOOLEAN VerifyBlocks; ULONG64 DumpOptions = 0xffffff; #define HEAP_DUMP_FREE_LISTS 1 #define HEAP_DUMP_GFLAGS 2 #define HEAP_DUMP_GTAGS 4 #define HEAP_DUMP_FREE_LISTS_DETAILS 8 BOOLEAN ScanVM; ULONG GetFieldSize ( IN LPCSTR Type, IN LPCSTR Field ) { FIELD_INFO flds = {(PUCHAR)Field, NULL, 0, DBG_DUMP_FIELD_FULL_NAME, 0, NULL}; SYM_DUMP_PARAM Sym = { sizeof (SYM_DUMP_PARAM), (PUCHAR)Type, DBG_DUMP_NO_PRINT, 0, NULL, NULL, NULL, 1, &flds }; ULONG RetVal; RetVal = Ioctl( IG_DUMP_SYMBOL_INFO, &Sym, Sym.size ); return flds.size; } ULONG ReadLongValue(LPTSTR Symbol) { ULONG64 Address; ULONG Value = 0; Address = GetExpression( Symbol ); if ( (Address == 0) || !ReadMemory( Address, &Value, sizeof( Value ), NULL )) { dprintf( "HEAPEXT: Unable to read %s\n", Symbol ); } return Value; } #define CheckAndPrintFlag(x)\ if (Flags & (x)) dprintf(#x" "); void DumpFlagDescription(ULONG Flags) { CheckAndPrintFlag(HEAP_NO_SERIALIZE); CheckAndPrintFlag(HEAP_GROWABLE); CheckAndPrintFlag(HEAP_GENERATE_EXCEPTIONS); CheckAndPrintFlag(HEAP_ZERO_MEMORY); CheckAndPrintFlag(HEAP_REALLOC_IN_PLACE_ONLY); CheckAndPrintFlag(HEAP_TAIL_CHECKING_ENABLED); CheckAndPrintFlag(HEAP_FREE_CHECKING_ENABLED); CheckAndPrintFlag(HEAP_DISABLE_COALESCE_ON_FREE); CheckAndPrintFlag(HEAP_CREATE_ALIGN_16); CheckAndPrintFlag(HEAP_CREATE_ENABLE_TRACING); } void DumpEntryFlagDescription(ULONG Flags) { if (Flags & HEAP_ENTRY_BUSY) dprintf("busy "); else dprintf("free "); if (Flags & HEAP_ENTRY_EXTRA_PRESENT) dprintf("extra "); if (Flags & HEAP_ENTRY_FILL_PATTERN) dprintf("fill "); if (Flags & HEAP_ENTRY_VIRTUAL_ALLOC) dprintf("virtual "); if (Flags & HEAP_ENTRY_LAST_ENTRY) dprintf("last "); if (Flags & HEAP_ENTRY_SETTABLE_FLAGS) dprintf("user_flag "); } void DumpEntryHeader() { dprintf("Entry User Heap Segment Size PrevSize Flags\n"); dprintf("----------------------------------------------------------------------\n"); } void DumpEntryInfo( IN ULONG64 HeapAddress, IN ULONG64 SegmentAddress, ULONG64 EntryAddress ) { ULONG SizeOfEntry; ULONG PreviousSize; ULONG Flags; ULONG Size; UCHAR SegmentIndex; UCHAR SmallTagIndex; SizeOfEntry = GetTypeSize("nt!_HEAP_ENTRY"); // same as granularity InitTypeRead(EntryAddress, nt!_HEAP_ENTRY); PreviousSize = (ULONG)ReadField(PreviousSize) * SizeOfEntry; Size = (ULONG) ReadField(Size) * SizeOfEntry; Flags = (ULONG) ReadField(Flags); SegmentIndex = (UCHAR) ReadField(SegmentIndex); SmallTagIndex = (UCHAR) ReadField(SmallTagCode); if (SegmentIndex != 0xff) { dprintf("%p %p %p %p %8lx %8lx ", EntryAddress, EntryAddress + SizeOfEntry, HeapAddress, SegmentAddress, Size, PreviousSize ); DumpEntryFlagDescription(Flags); if (Lookaside) { dprintf(" - lookaside "); } } else { ULONG64 SubSegment = ReadField(SubSegment); ULONG64 BlockSize; GetFieldValue(SubSegment, "ntdll!_HEAP_SUBSEGMENT", "BlockSize", BlockSize); Size = (ULONG)BlockSize * SizeOfEntry; dprintf("%p %p %p %p %8lx - ", EntryAddress, EntryAddress + SizeOfEntry, HeapAddress, SegmentAddress, Size ); if (SmallTagIndex) { dprintf("LFH_BUSY; "); } else { dprintf("LFH_FREE; "); } DumpEntryFlagDescription(Flags); } dprintf("\n"); } void DumpHeapStructure (ULONG64 HeapAddress) { ULONG AlignRound, Offset; if (InitTypeRead(HeapAddress, nt!_HEAP)) { return; } GetFieldOffset("nt!_HEAP", "VirtualAllocdBlocks", &Offset); AlignRound = (ULONG)ReadField(AlignRound) - GetTypeSize( "nt!_HEAP_ENTRY" ); if ((ULONG)ReadField(Flags) & HEAP_TAIL_CHECKING_ENABLED) { AlignRound -= CHECK_HEAP_TAIL_SIZE; } AlignRound += 1; dprintf( " Flags: %08x ", (ULONG)ReadField(Flags) ); DumpFlagDescription((ULONG)ReadField(Flags)); dprintf("\n"); dprintf( " ForceFlags: %08x ", (ULONG)ReadField(ForceFlags) ); DumpFlagDescription((ULONG)ReadField(ForceFlags)); dprintf("\n"); dprintf( " Granularity: %u bytes\n", AlignRound ); dprintf( " Segment Reserve: %08x\n", (ULONG)ReadField(SegmentReserve)); dprintf( " Segment Commit: %08x\n", (ULONG)ReadField(SegmentCommit) ); dprintf( " DeCommit Block Thres:%08x\n", (ULONG)ReadField(DeCommitFreeBlockThreshold) ); dprintf( " DeCommit Total Thres:%08x\n", (ULONG)ReadField(DeCommitTotalFreeThreshold) ); dprintf( " Total Free Size: %08x\n", (ULONG)ReadField(TotalFreeSize) ); dprintf( " Max. Allocation Size:%08x\n", (ULONG)ReadField(MaximumAllocationSize) ); dprintf( " Lock Variable at: %p\n", ReadField(LockVariable) ); dprintf( " Next TagIndex: %04x\n", (ULONG)ReadField(NextAvailableTagIndex) ); dprintf( " Maximum TagIndex: %04x\n", (ULONG)ReadField(MaximumTagIndex) ); dprintf( " Tag Entries: %08x\n", (ULONG)ReadField(TagEntries) ); dprintf( " PsuedoTag Entries: %08x\n", (ULONG)ReadField(PseudoTagEntries) ); dprintf( " Virtual Alloc List: %p\n", HeapAddress + Offset); if (DumpOptions & (HEAP_DUMP_FREE_LISTS | HEAP_DUMP_FREE_LISTS_DETAILS)) { ULONG i, ListSize, FreeListOffset; dprintf( " FreeList Usage: %08x %08x %08x %08x\n", (ULONG)ReadField(u.FreeListsInUseUlong[0]), (ULONG)ReadField(u.FreeListsInUseUlong[1]), (ULONG)ReadField(u.FreeListsInUseUlong[2]), (ULONG)ReadField(u.FreeListsInUseUlong[3]) ); GetFieldOffset ("nt!_HEAP", "FreeLists", &Offset); ListSize = GetTypeSize("nt!_LIST_ENTRY"); GetFieldOffset ("nt!_HEAP_FREE_ENTRY", "FreeList", &FreeListOffset); for (i=0; i MaxSize) MaxSize = Size; } if (DumpOptions & HEAP_DUMP_FREE_LISTS_DETAILS) { dprintf( " %08p: %05x . %05x [%02x] - ", FreeEntryAddress, PrevSize, Size, Flags ); DumpEntryFlagDescription(Flags); dprintf("\n"); } Count += 1; ReadPointer(Next, &Next); if (CheckControlC()) { return; } } if (Count) { if (DumpOptions & HEAP_DUMP_FREE_LISTS_DETAILS) { dprintf(" "); } dprintf( " * Total %ld block(s) (%08lx, %08lx)\n", Count, MinSize, MaxSize ); } } } } } void DumpGlobals() { ULONG64 pNtGlobalFlag, NtGlobalFlag = 0; ULONG64 pNtTempGlobal, NtTempGlobal = 0; NtGlobalFlag = ReadLongValue("NTDLL!NtGlobalFlag"); if ((NtGlobalFlag & (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS | FLG_HEAP_VALIDATE_ALL | FLG_HEAP_ENABLE_TAGGING | FLG_USER_STACK_TRACE_DB | FLG_HEAP_DISABLE_COALESCING )) != 0 ) { dprintf( "NtGlobalFlag enables following debugging aids for new heaps:" ); if (NtGlobalFlag & FLG_HEAP_ENABLE_TAIL_CHECK) { dprintf( " tail checking\n" ); } if (NtGlobalFlag & FLG_HEAP_ENABLE_FREE_CHECK) { dprintf( " free checking\n" ); } if (NtGlobalFlag & FLG_HEAP_VALIDATE_PARAMETERS) { dprintf( " validate parameters\n" ); } if (NtGlobalFlag & FLG_HEAP_VALIDATE_ALL) { dprintf( " validate on call\n" ); } if (NtGlobalFlag & FLG_HEAP_ENABLE_TAGGING) { dprintf( " heap tagging\n" ); } if (NtGlobalFlag & FLG_USER_STACK_TRACE_DB) { dprintf( " stack back traces\n" ); } if (NtGlobalFlag & FLG_HEAP_DISABLE_COALESCING) { dprintf( " disable coalescing of free blocks\n" ); } } NtTempGlobal = ReadLongValue( "NTDLL!RtlpDisableHeapLookaside" ); if (NtTempGlobal) { dprintf( "The process has the following heap extended settings %08lx:\n", (ULONG)NtTempGlobal ); if (NtTempGlobal & 1) { dprintf(" - Lookasides disabled\n"); } if (NtTempGlobal & 2) { dprintf(" - Large blocks cache disabled\n"); } if (NtTempGlobal & 8) { dprintf(" - Low Fragmentation Heap activated for all heaps\n"); } dprintf("\n"); } pNtTempGlobal = GetExpression( "NTDLL!RtlpAffinityState" ); if ( pNtTempGlobal ) { ULONG64 TempValue; ULONG64 AffinitySwaps; ULONG64 AffinityResets; ULONG64 AffinityAllocs; GetFieldValue(pNtTempGlobal, "ntdll!_AFFINITY_STATE", "Limit", TempValue); if (TempValue) { dprintf("Affinity manager status:\n"); dprintf(" - Virtual affinity limit %I64ld\n", TempValue); GetFieldValue(pNtTempGlobal, "ntdll!_AFFINITY_STATE", "CrtLimit", TempValue); dprintf(" - Current entries in use %ld\n", (LONG)TempValue); GetFieldValue(pNtTempGlobal, "ntdll!_AFFINITY_STATE", "AffinitySwaps", AffinitySwaps); GetFieldValue(pNtTempGlobal, "ntdll!_AFFINITY_STATE", "AffinityResets", AffinityResets); GetFieldValue(pNtTempGlobal, "ntdll!_AFFINITY_STATE", "AffinityAllocs", AffinityAllocs); dprintf(" - Statistics: Swaps=%I64ld, Resets=%I64ld, Allocs=%I64ld\n\n", AffinitySwaps, AffinityResets, AffinityAllocs ); } } } BOOLEAN HeapFindRoutine( IN ULONG Context, IN ULONG64 HeapAddress, IN ULONG64 SegmentAddress, IN ULONG64 EntryAddress, IN ULONG64 Data ) { switch (Context) { case CONTEXT_START_HEAP: // // we found a block, we won't need then to search in other heaps // if (HeapEntryFind) { ScanLevel = 0; } break; case CONTEXT_FREE_BLOCK: case CONTEXT_BUSY_BLOCK: case CONTEXT_LOOKASIDE_BLOCK: case CONTEXT_VIRTUAL_BLOCK: if ((AddressToFind >= EntryAddress) && (AddressToFind < (EntryAddress + Data))) { if (HeapEntryFind == 0) { HeapAddressFind = HeapAddress; SegmentAddressFind = SegmentAddress; } if (Context == CONTEXT_LOOKASIDE_BLOCK) { Lookaside = TRUE; ScanLevel = 0; } HeapEntryFind = EntryAddress; HeapEntryFindSize = Data; } break; case CONTEXT_ERROR: dprintf("HEAP %p (Seg %p) At %p Error: %s\n", HeapAddress, SegmentAddress, EntryAddress, (LPSTR) (ULONG_PTR) Data ); break; } return TRUE; } BOOLEAN SearchVMReference ( HANDLE hProcess, ULONG64 Base, ULONG64 EndAddress ) { NTSTATUS Status; SIZE_T BufferLen; ULONG_PTR lpAddress = 0; MEMORY_BASIC_INFORMATION Buffer; PVOID MemoryBuffer; if ( hProcess ) { MemoryBuffer = malloc(PageSize); if (!MemoryBuffer) { dprintf("Not enough memory. Abording.\n"); return FALSE; } dprintf("Search VM for address range %p - %p : ",Base, EndAddress); BufferLen = sizeof(Buffer); while (BufferLen) { BufferLen = VirtualQueryEx( hProcess, (LPVOID)lpAddress, &Buffer, sizeof(Buffer) ); if (BufferLen) { if ((Buffer.AllocationProtect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) /*&& (Buffer.Type == MEM_PRIVATE)*/) { ULONG64 NumPages; ULONG i, j; NumPages = Buffer.RegionSize / PageSize; for (i = 0; i < NumPages; i++) { if (ReadMemory( (ULONG64)(lpAddress + i * PageSize), MemoryBuffer, PageSize, NULL )) { ULONG_PTR * Pointers = (ULONG_PTR *)MemoryBuffer; for (j = 0; j < PageSize/sizeof(ULONG_PTR); j++) { ULONG_PTR Address = lpAddress + i * PageSize + j * sizeof(ULONG_PTR); if ((*Pointers >= Base) && (*Pointers <= EndAddress)) { dprintf("%08lx (%08lx), ", Address, *Pointers ); } Pointers += 1; } } } } lpAddress += Buffer.RegionSize; } } dprintf("\n"); free(MemoryBuffer); } return TRUE; } VOID HeapStat(LPCTSTR szArguments); VOID HeapFindBlock(LPCTSTR szArguments) { ULONG64 Process; ULONG64 ThePeb; HANDLE hProcess; if (!InitializeHeapExtension()) { return; } HeapEntryFind = 0; HeapEntryFindSize = 0; HeapAddressFind = 0; SegmentAddressFind = 0; Lookaside = FALSE; AddressToFind = 0; GetPebAddress( 0, &ThePeb); GetCurrentProcessHandle( &hProcess ); ScanVM = FALSE; { LPSTR p = (LPSTR)szArguments; while (p && *p) { if (*p == '-') { p++; if (toupper(*p) == 'V') { ScanVM = TRUE; } } else if (isxdigit(*p)) { GetExpressionEx(p, &AddressToFind, &p); continue; } p++; } } if (AddressToFind == 0) { dprintf("Syntax:\n!heap -x [-v] address\n"); return; } ScanProcessHeaps( 0, ThePeb, HeapFindRoutine ); if (HeapEntryFind) { DumpEntryHeader(); DumpEntryInfo(HeapAddressFind, SegmentAddressFind, HeapEntryFind); dprintf("\n"); if (ScanVM) { SearchVMReference(hProcess, HeapEntryFind, HeapEntryFind + HeapEntryFindSize - 1); } } else { if (ScanVM) { SearchVMReference(hProcess, AddressToFind, AddressToFind); } } } // // Heap stat implementation // typedef struct _SIZE_INFO { ULONG Busy; ULONG Free; ULONG FrontHeapAllocs; ULONG FrontHeapFrees; }SIZE_INFO, *PSIZE_INFO; typedef struct _HEAP_STATS { ULONG HeapIndex; ULONG64 HeapAddress; ULONG64 ReservedMemory; ULONG64 CommitedMemory; ULONG64 VirtualBytes; ULONG64 FreeSpace; ULONG Flags; ULONG FreeListLength; ULONG VirtualBlocks; ULONG UncommitedRanges; ULONG64 LockContention; ULONG FrontEndHeapType; ULONG64 FrontEndHeap; BOOLEAN ScanningSubSegment; }HEAP_STATS, *PHEAP_STATS; HEAP_STATS CrtHeapStat; PSIZE_INFO SizeInfo = NULL; ULONG BucketSize; ULONG LargestBucketIndex = 0; ULONG64 StatHeapAddress; ULONG DumpBlocksSize = 0; ULONG FASTCALL HeapStatSizeToSizeIndex(ULONG64 Size) { ULONG Index = (ULONG)(Size / BucketSize); if (Index >= LargestBucketIndex) { Index = LargestBucketIndex - 1; } return Index; } VOID HeapStatDumpBlocks() { ULONG Index; ULONG64 Busy = 0, Free = 0, FEBusy = 0, FEFree = 0; dprintf("\n Default heap Front heap \n"); dprintf(" Range (bytes) Busy Free Busy Free \n"); dprintf("----------------------------------------------- \n"); if (SizeInfo == NULL) { return; } for ( Index = 0; Index < LargestBucketIndex; Index++ ) { if (SizeInfo[Index].Busy || SizeInfo[Index].Free || SizeInfo[Index].FrontHeapAllocs || SizeInfo[Index].FrontHeapFrees) { dprintf("%6ld - %6ld %6ld %6ld %6ld %6ld\n", Index * BucketSize, (Index + 1) * BucketSize, SizeInfo[Index].Busy, SizeInfo[Index].Free, SizeInfo[Index].FrontHeapAllocs, SizeInfo[Index].FrontHeapFrees ); FEBusy += SizeInfo[Index].FrontHeapAllocs; FEFree += SizeInfo[Index].FrontHeapFrees; Busy += SizeInfo[Index].Busy; Free += SizeInfo[Index].Free; } } dprintf("----------------------------------------------- \n"); dprintf(" Total %6I64d %6I64d %6I64d %6I64d \n", Busy, Free, FEBusy, FEFree ); } VOID DumpBlock ( IN ULONG64 BlockAddress, IN UCHAR Flag ) { UCHAR Buffer[33]; PULONG pLongArray = (PULONG)Buffer; ULONG i; memchr(Buffer, '?', sizeof( Buffer )); if (!ReadMemory( BlockAddress, &Buffer[0], sizeof( Buffer ), NULL )) { dprintf("%p ?\n", BlockAddress); return; } dprintf("%p %c %08lx %08lx %08lx %08lx ", BlockAddress, Flag, pLongArray[0], pLongArray[1], pLongArray[2], pLongArray[3] ); for (i = 0; i < 32; i++) { if (!isprint(Buffer[i])) { Buffer[i] = '.'; } } Buffer[32] = 0; dprintf("%s\n", Buffer); } VOID CollectHeapInfo( ULONG64 HeapAddress ) { ULONG64 TempValue; ULONG64 FrontEndHeapType; memset(&CrtHeapStat, 0, sizeof(CrtHeapStat)); CrtHeapStat.HeapAddress = HeapAddress; GetFieldValue(HeapAddress, "ntdll!_HEAP", "TotalFreeSize", CrtHeapStat.FreeSpace); CrtHeapStat.FreeSpace *= HeapEntrySize; GetFieldValue(HeapAddress, "ntdll!_HEAP", "NonDedicatedListLength", CrtHeapStat.FreeListLength); GetFieldValue(HeapAddress, "ntdll!_HEAP", "Flags", TempValue); CrtHeapStat.Flags = (ULONG)TempValue; GetFieldValue(HeapAddress, "ntdll!_HEAP", "Signature", TempValue); if (TempValue != 0xeeffeeff) { dprintf("Error: Heap %p has an invalid signature %08lx\n", HeapAddress, 0xeeffeeff); } if (GetFieldValue(HeapAddress, "ntdll!_HEAP", "LockVariable", TempValue) == 0) { if (TempValue != 0) { GetFieldValue(TempValue, "ntdll!_RTL_CRITICAL_SECTION", "DebugInfo", TempValue); GetFieldValue(TempValue, "ntdll!_RTL_CRITICAL_SECTION_DEBUG", "ContentionCount", CrtHeapStat.LockContention); } else { CrtHeapStat.LockContention = 0xbad; } } CrtHeapStat.FrontEndHeapType = 0; if (GetFieldValue(HeapAddress, "ntdll!_HEAP", "Lookaside", TempValue)) { if (GetFieldValue(HeapAddress, "ntdll!_HEAP", "FrontEndHeapType", FrontEndHeapType)) { dprintf("Front-end heap type info is not available\n"); } else { CrtHeapStat.FrontEndHeapType = (ULONG)FrontEndHeapType; GetFieldValue(HeapAddress, "ntdll!_HEAP", "FrontEndHeap", CrtHeapStat.FrontEndHeap); } } else { if (TempValue) { CrtHeapStat.FrontEndHeapType = 1; } CrtHeapStat.FrontEndHeap = TempValue; } } typedef struct _BUCKET_INFO { ULONG TotalBlocks; ULONG SubSegmentCounts; ULONG BlockUnits; ULONG UseAffinity; LONG Conversions; } BUCKET_INFO, *PBUCKET_INFO; VOID DumpLowfHeap( ULONG64 HeapAddress ) { ULONG64 TempValue, CrtAddress; ULONG64 Head; ULONG TempOffset, i; ULONG64 Next; ULONG Counter, TempSize; ULONG Values[20]; PBUCKET_INFO BucketInfo; ULONG CacheSize = GetFieldSize("ntdll!_USER_MEMORY_CACHE", "AvailableBlocks") / sizeof(ULONG); if (CacheSize > 20) { CacheSize = 20; } InitTypeRead(HeapAddress, nt!_LFH_HEAP); if (GetFieldValue(HeapAddress, "ntdll!_LFH_HEAP", "Lock.DebugInfo", TempValue) == 0) { if (TempValue != 0) { ULONG64 Contention; GetFieldValue(TempValue, "ntdll!_RTL_CRITICAL_SECTION_DEBUG", "ContentionCount", Contention); dprintf(" Lock contention %7ld\n", (ULONG) Contention); } } GetFieldValue(HeapAddress, "ntdll!_LFH_HEAP", "SubSegmentZones.Flink", Head); Counter = 0; ReadPointer(Head, &Next); while (Next != Head) { Counter += 1; if (!ReadPointer(Next, &Next)) { dprintf("ERROR Cannot read SubSegmentZones list at %p\n", Next); break; } } dprintf(" Metadata usage %7ld\n", Counter * 1024); dprintf(" Statistics:\n"); dprintf(" Segments created %7ld\n", ReadField(SegmentCreate)); dprintf(" Segments deleted %7ld\n", ReadField(SegmentDelete)); dprintf(" Segments reused %7ld\n", ReadField(SegmentInsertInFree)); dprintf(" Conversions %7ld\n", ReadField(Conversions)); dprintf(" ConvertedSpace %7ld\n\n", ReadField(ConvertedSpace)); GetFieldOffset("ntdll!_LFH_HEAP", "UserBlockCache", &TempOffset); CrtAddress = TempValue = HeapAddress + TempOffset; InitTypeRead(TempValue, nt!_USER_MEMORY_CACHE); dprintf(" Block cache:\n"); dprintf(" Free blocks %7ld\n", ReadField(FreeBlocks)); dprintf(" Sequence %7ld\n", ReadField(Sequence)); dprintf(" Cache blocks"); for (i = 0; i < CacheSize; i++) { ULONG64 Depth; GetFieldValue(TempValue, "ntdll!_SLIST_HEADER", "Depth", Depth); dprintf(" %6ld", (ULONG)Depth); TempValue += GetTypeSize("_SLIST_HEADER"); } GetFieldOffset("ntdll!_USER_MEMORY_CACHE", "AvailableBlocks", &TempOffset); dprintf("\n Available "); TempValue = CrtAddress + TempOffset; if (ReadMemory( TempValue, Values, CacheSize * sizeof(ULONG), NULL )) { for (i = 0; i < CacheSize; i++) { dprintf(" %6ld", Values[i]); } } dprintf("\n"); GetFieldOffset("ntdll!_LFH_HEAP", "Buckets", &TempOffset); CrtAddress = HeapAddress + TempOffset; TempSize = GetTypeSize("nt!_HEAP_BUCKET"); BucketInfo = (PBUCKET_INFO)malloc(128 * sizeof(BUCKET_INFO)); if (BucketInfo) { dprintf(" Buckets info:\n"); dprintf(" Size Blocks Seg Aff Conv\n"); dprintf("-----------------------------\n"); for (i = 0; i < 128; i++) { InitTypeRead(CrtAddress + (i * TempSize), nt!_HEAP_BUCKET); BucketInfo[i].TotalBlocks = (ULONG)ReadField(Counters.TotalBlocks); BucketInfo[i].BlockUnits = (ULONG)ReadField(BlockUnits); BucketInfo[i].Conversions = (ULONG)ReadField(Conversions); BucketInfo[i].SubSegmentCounts = (ULONG)ReadField(Counters.SubSegmentCounts); BucketInfo[i].UseAffinity = (ULONG)ReadField(UseAffinity); if (BucketInfo[i].TotalBlocks) { dprintf("%6ld %7ld %5ld %ld %4ld\n", BucketInfo[i].BlockUnits*HeapEntrySize, BucketInfo[i].TotalBlocks, BucketInfo[i].SubSegmentCounts, BucketInfo[i].UseAffinity, BucketInfo[i].Conversions ); } } dprintf("-----------------------------\n"); free(BucketInfo); } } VOID DumpHeapInfo( ULONG64 HeapAddress ) { ULONG64 TempValue; dprintf("\n%2ld: Heap %p\n", CrtHeapStat.HeapIndex, CrtHeapStat.HeapAddress); dprintf(" Flags %08lx - ", CrtHeapStat.Flags); DumpFlagDescription(CrtHeapStat.Flags); dprintf("\n"); dprintf(" Reserved %I64d (k)\n", CrtHeapStat.ReservedMemory/1024); dprintf(" Commited %I64d (k)\n", CrtHeapStat.CommitedMemory/1024); dprintf(" Virtual bytes %I64d (k)\n", CrtHeapStat.VirtualBytes/1024); dprintf(" Free space %I64d (k)\n", CrtHeapStat.FreeSpace/1024); if (CrtHeapStat.CommitedMemory) { dprintf(" External fragmentation %ld%% (%ld free blocks)\n", (ULONG)(CrtHeapStat.FreeSpace *100 / CrtHeapStat.CommitedMemory), CrtHeapStat.FreeListLength ); } if (CrtHeapStat.VirtualBytes) { dprintf(" Virtual address fragmentation %ld%% (%ld uncommited ranges)\n", (ULONG)((CrtHeapStat.VirtualBytes - CrtHeapStat.CommitedMemory) *100 / CrtHeapStat.VirtualBytes), CrtHeapStat.UncommitedRanges ); } dprintf(" Virtual blocks %ld\n", CrtHeapStat.VirtualBlocks); dprintf(" Lock contention %ld\n", CrtHeapStat.LockContention); GetFieldValue(HeapAddress, "ntdll!_HEAP", "LastSegmentIndex", TempValue); dprintf(" Segments %ld\n", (ULONG)TempValue + 1); GetFieldValue(HeapAddress, "ntdll!_HEAP", "LargeBlocksIndex", TempValue); if (TempValue) { ULONG PerfOffset; InitTypeRead(TempValue, nt!_HEAP_INDEX); dprintf(" %ld hash table for the free list\n", (ULONG) ReadField(ArraySize)); dprintf(" Commits %ld\n", (ULONG) ReadField(Committs)); dprintf(" Decommitts %ld\n", (ULONG) ReadField(Decommitts)); } switch (CrtHeapStat.FrontEndHeapType) { case 1: dprintf("\n Lookaside heap %p\n", CrtHeapStat.FrontEndHeap); break; case 2: dprintf("\n Low fragmentation heap %p\n", CrtHeapStat.FrontEndHeap); DumpLowfHeap(CrtHeapStat.FrontEndHeap); break; } } BOOLEAN HeapStatRoutine( IN ULONG Context, IN ULONG64 HeapAddress, IN ULONG64 SegmentAddress, IN ULONG64 EntryAddress, IN ULONG64 Data ) { ULONG SizeIndex; switch (Context) { case CONTEXT_START_HEAP: { if (DumpBlocksSize == 0) { dprintf("Walking the heap %p ", HeapAddress); } CollectHeapInfo(HeapAddress); // // Allow scanning the heap // return TRUE; } break; case CONTEXT_END_HEAP: if (DumpBlocksSize == 0) { dprintf("\r"); } if (SizeInfo == NULL) { dprintf("%p %08lx %7I64d %6I64d %6I64d %6I64d %5ld %5ld %4ld %6lx ", CrtHeapStat.HeapAddress, CrtHeapStat.Flags, (CrtHeapStat.ReservedMemory / 1024), (CrtHeapStat.CommitedMemory / 1024), (CrtHeapStat.VirtualBytes / 1024), (CrtHeapStat.FreeSpace / 1024), CrtHeapStat.FreeListLength, CrtHeapStat.UncommitedRanges, CrtHeapStat.VirtualBlocks, CrtHeapStat.LockContention ); switch (CrtHeapStat.FrontEndHeapType) { case 1: dprintf("L "); break; case 2: dprintf("LFH"); break; default: dprintf(" "); } dprintf("\n"); // // Report external fragmentation is the heap uses more than 1M // The threshold to report is 10% // if ((CrtHeapStat.CommitedMemory > 1024*1024) && CrtHeapStat.FreeSpace *100 / CrtHeapStat.CommitedMemory > 10) { dprintf(" External fragmentation %ld %% (%ld free blocks)\n", (ULONG)(CrtHeapStat.FreeSpace *100 / CrtHeapStat.CommitedMemory), CrtHeapStat.FreeListLength ); } // // Report virtual address fragmentation is the heap has more than 100 UCR // The threshold to report is 10% // if (CrtHeapStat.UncommitedRanges > 100 && (CrtHeapStat.VirtualBytes - CrtHeapStat.CommitedMemory) *100 / CrtHeapStat.VirtualBytes > 10) { dprintf(" Virtual address fragmentation %ld %% (%ld uncommited ranges)\n", (ULONG)((CrtHeapStat.VirtualBytes - CrtHeapStat.CommitedMemory) *100 / CrtHeapStat.VirtualBytes), CrtHeapStat.UncommitedRanges ); } // // Make noise about lock contention. The value is 1M, and it's arbitrary. // Over a long run this value can be legitimate // if (CrtHeapStat.LockContention > 1024*1024) { dprintf(" Lock contention %ld \n", CrtHeapStat.LockContention); } } else { DumpHeapInfo(StatHeapAddress); HeapStatDumpBlocks(); } break; case CONTEXT_START_SEGMENT: { ULONG64 NumberOfPages, NumberOfUnCommittedPages, LargestUnCommittedRange, NumberOfUnCommittedRanges; GetFieldValue(SegmentAddress, "ntdll!_HEAP_SEGMENT", "NumberOfPages", NumberOfPages); GetFieldValue(SegmentAddress, "ntdll!_HEAP_SEGMENT", "NumberOfUnCommittedPages", NumberOfUnCommittedPages); GetFieldValue(SegmentAddress, "ntdll!_HEAP_SEGMENT", "LargestUnCommittedRange", LargestUnCommittedRange); GetFieldValue(SegmentAddress, "ntdll!_HEAP_SEGMENT", "NumberOfUnCommittedRanges", NumberOfUnCommittedRanges); CrtHeapStat.ReservedMemory += NumberOfPages * PageSize; CrtHeapStat.CommitedMemory += (NumberOfPages - NumberOfUnCommittedPages) * PageSize; CrtHeapStat.UncommitedRanges += (ULONG)NumberOfUnCommittedRanges; CrtHeapStat.VirtualBytes += NumberOfPages * PageSize - LargestUnCommittedRange; } if ((SizeInfo != NULL) && (DumpBlocksSize == 0) ) { dprintf("."); } if (VerifyBlocks) { dprintf("."); return TRUE; } // // Do not walk the blocks. We need to return FALSE // return (SizeInfo != NULL); case CONTEXT_START_SUBSEGMENT: CrtHeapStat.ScanningSubSegment = TRUE; break; case CONTEXT_END_SUBSEGMENT: CrtHeapStat.ScanningSubSegment = FALSE; break; case CONTEXT_FREE_BLOCK: if (SizeInfo) { if (CrtHeapStat.ScanningSubSegment) { SizeInfo[HeapStatSizeToSizeIndex(Data)].FrontHeapFrees += 1; } else { SizeInfo[HeapStatSizeToSizeIndex(Data)].Free += 1; } } if (Data == DumpBlocksSize) { DumpBlock(EntryAddress + HeapEntrySize, 'F'); } break; case CONTEXT_BUSY_BLOCK: if (SizeInfo) { if (CrtHeapStat.ScanningSubSegment) { SizeInfo[HeapStatSizeToSizeIndex(Data)].FrontHeapAllocs += 1; } else { SizeInfo[HeapStatSizeToSizeIndex(Data)].Busy += 1; } } if (Data == DumpBlocksSize) { DumpBlock(EntryAddress + HeapEntrySize, 'B'); } break; case CONTEXT_LOOKASIDE_BLOCK: if (SizeInfo) { SizeInfo[HeapStatSizeToSizeIndex(Data)].FrontHeapFrees += 1; SizeInfo[HeapStatSizeToSizeIndex(Data)].Busy -= 1; } if (Data == DumpBlocksSize) { DumpBlock(EntryAddress + HeapEntrySize, 'f'); } break; case CONTEXT_VIRTUAL_BLOCK: if (SizeInfo) { SizeInfo[HeapStatSizeToSizeIndex(Data)].Busy += 1; } CrtHeapStat.VirtualBlocks += 1; break; case CONTEXT_ERROR: dprintf("HEAP %p (Seg %p) At %p Error: %s\n", HeapAddress, SegmentAddress, EntryAddress, (LPSTR) (ULONG_PTR) Data ); break; } return TRUE; } VOID HeapStat(LPCTSTR szArguments) { ULONG64 Process; ULONG64 ThePeb; HANDLE hProcess; int LastCommand = ' '; if (!InitializeHeapExtension()) { return; } HeapEntryFind = 0; HeapEntryFindSize = 0; HeapAddressFind = 0; SegmentAddressFind = 0; Lookaside = FALSE; StatHeapAddress = 0; BucketSize = 1024; DumpBlocksSize = 0; VerifyBlocks = FALSE; GetPebAddress( 0, &ThePeb); GetCurrentProcessHandle( &hProcess ); ScanVM = FALSE; { LPSTR p = (LPSTR)szArguments; while (p && *p) { if (*p == '-') { p++; LastCommand = toupper(*p); if (LastCommand == 'V') { VerifyBlocks = TRUE; } } else if (isxdigit(*p)) { ULONG64 HexInput = 0; GetExpressionEx(p, &HexInput, &p); switch (LastCommand) { case 'B': BucketSize = (ULONG)HexInput; break; case 'D': DumpBlocksSize = (ULONG)HexInput; break; default: if (StatHeapAddress != 0) { dprintf("Parameter error: unexpected second heap address %I64d\n", HexInput); } else { StatHeapAddress = HexInput; } } LastCommand = '\0'; continue; } p++; } } if (StatHeapAddress == 0) { DumpGlobals(); if (PointerSize == 8) { dprintf(" "); } dprintf(" Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast \n"); if (PointerSize == 8) { dprintf(" "); } dprintf(" (k) (k) (k) (k) length blocks cont. heap \n"); if (PointerSize == 8) { dprintf("--------"); } dprintf("-----------------------------------------------------------------------------\n"); ScanProcessHeaps( 0, ThePeb, HeapStatRoutine ); if (PointerSize == 8) { dprintf("--------"); } dprintf("-----------------------------------------------------------------------------\n"); } else { // // Do not handle blocks over 512k, which is close to virtual alloc limit // LargestBucketIndex = (512*1024)/BucketSize; SizeInfo = malloc(LargestBucketIndex * sizeof(SIZE_INFO)); if (SizeInfo == NULL) { dprintf("cannot allocate thye statistics buffer\n"); return; } memset(SizeInfo, 0, LargestBucketIndex * sizeof(SIZE_INFO)); ScanProcessHeaps( StatHeapAddress, ThePeb, HeapStatRoutine ); free(SizeInfo); SizeInfo = NULL; } }