windows-nt/Source/XPSP1/NT/sdktools/debuggers/exts/extsdll/heapfind.c
2020-09-26 16:20:57 +08:00

1495 lines
39 KiB
C

/*++
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("_HEAP_ENTRY"); // same as granularity
InitTypeRead(EntryAddress, _HEAP_ENTRY);
PreviousSize = (ULONG)ReadField(PreviousSize) * SizeOfEntry;
Size = (ULONG) ReadField(Size) * SizeOfEntry;
Flags = (ULONG) ReadField(Flags);
SegmentIndex = (UCHAR) ReadField(SegmentIndex);
SmallTagIndex = (UCHAR) ReadField(SmallTagIndex);
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, _HEAP)) {
return;
}
GetFieldOffset("_HEAP", "VirtualAllocdBlocks", &Offset);
AlignRound = (ULONG)ReadField(AlignRound) - GetTypeSize( "_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 ("_HEAP", "FreeLists", &Offset);
ListSize = GetTypeSize("LIST_ENTRY");
GetFieldOffset ("_HEAP_FREE_ENTRY", "FreeList", &FreeListOffset);
for (i=0; i<HEAP_MAXIMUM_FREELISTS; i++) {
ULONG64 Flink, Blink, Next;
ULONG64 FreeListHead;
ULONG Count = 0;
ULONG MinSize = 0, MaxSize = 0;
FreeListHead = HeapAddress + Offset + ListSize * i;
GetFieldValue(FreeListHead, "LIST_ENTRY", "Flink", Flink);
GetFieldValue(FreeListHead, "LIST_ENTRY", "Blink", Blink);
if (Flink != FreeListHead) {
dprintf( " FreeList[ %02x ] at %08p",
i,
FreeListHead
);
if (DumpOptions & HEAP_DUMP_FREE_LISTS_DETAILS) {
dprintf("\n");
}
Next = Flink;
while (Next != FreeListHead) {
ULONG Size, PrevSize, Flags;
ULONG64 FreeEntryAddress;
FreeEntryAddress = Next - FreeListOffset;
if (InitTypeRead ( FreeEntryAddress, _HEAP_FREE_ENTRY)) {
if (Count) {
dprintf( " Total: %ld blocks (%08lx, %08lx) * Error reading %p\n",
Count,
MinSize,
MaxSize,
FreeEntryAddress
);
} else {
dprintf( " No blocks in list. Error reading %p\n",
FreeEntryAddress
);
}
break;
}
Size = (ULONG)ReadField(Size) * AlignRound;
PrevSize = (ULONG)ReadField(PreviousSize) * AlignRound;
Flags = (ULONG)ReadField(Flags);
if (!Count) {
MinSize = MaxSize = Size;
} else {
if (Size < MinSize) MinSize = Size;
if (Size > 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,
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)) {
sscanf( p, "%I64lx", &AddressToFind );
while ((*p) && isxdigit(*p)) {
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, 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, _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;
ReadPtr(Head, &Next);
while (Next != Head) {
Counter += 1;
if (ReadPtr(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, _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("_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), _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, _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,
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;
sscanf( p, "%I64lx", &HexInput );
while ((*p) && isxdigit(*p)) {
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;
}
}
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;
}
}