34 typedef struct _STRING_TABLE_ENTRY
54 static HANDLE DebugConsoleThreadHandle;
59 static PPH_LIST NewObjectList = NULL;
63 static BOOLEAN ShowAllLeaks =
FALSE;
64 static BOOLEAN InLeakDetection =
FALSE;
65 static ULONG NumberOfLeaks;
66 static ULONG NumberOfLeaksShown;
78 menu = GetSystemMenu(GetConsoleWindow(),
FALSE);
79 EnableMenuItem(menu, SC_CLOSE, MF_GRAYED | MF_DISABLED);
80 DeleteMenu(menu, SC_CLOSE, 0);
85 freopen(
"CONOUT$",
"w", stdout);
86 freopen(
"CONOUT$",
"w", stderr);
87 freopen(
"CONIN$",
"r", stdin);
94 consoleWindow = GetConsoleWindow();
97 if (IsIconic(consoleWindow))
98 ShowWindow(consoleWindow, SW_RESTORE);
100 BringWindowToTop(consoleWindow);
118 _In_ DWORD dwCtrlType
124 case CTRL_BREAK_EVENT:
125 case CTRL_CLOSE_EVENT:
133 static BOOLEAN NTAPI PhpLoadCurrentProcessSymbolsCallback(
135 _In_opt_ PVOID Context
139 (ULONG64)Module->BaseAddress, Module->Size);
144 static PWSTR PhpGetSymbolForAddress(
149 DebugConsoleSymbolProvider, (ULONG64)Address, NULL, NULL, NULL, NULL
153 static VOID PhpPrintObjectInfo(
155 _In_
LONG RefToSubtract
163 wprintf(L
"%Ix",
object);
166 wprintf(L
"\t% 20s", objectType->
Name);
173 wprintf(L
"\t%4d %c", ObjectHeader->RefCount - RefToSubtract, c);
185 wprintf(L
"\t%.32s", ((
PPH_STRING)
object)->Buffer);
189 wprintf(L
"\t%.32S", ((
PPH_BYTES)
object)->Buffer);
193 wprintf(L
"\tCount: %u", ((
PPH_LIST)
object)->Count);
223 static VOID PhpDumpObjectInfo(
235 wprintf(L
"Type: %s\n", objectType->
Name);
236 wprintf(L
"Reference count: %d\n", ObjectHeader->RefCount);
237 wprintf(L
"Flags: %x\n", ObjectHeader->Flags);
242 wprintf(L
"Number of objects: %u\n", ((
PPH_OBJECT_TYPE)
object)->NumberOfObjects);
245 wprintf(L
"Free list count: %u\n", ((
PPH_OBJECT_TYPE)
object)->FreeList.Count);
249 wprintf(L
"%s\n", ((
PPH_STRING)
object)->Buffer);
253 wprintf(L
"%S\n", ((
PPH_BYTES)
object)->Buffer);
260 __except (EXCEPTION_EXECUTE_HANDLER)
262 wprintf(L
"Error.\n");
271 ULONG expectedLookupMisses = 0;
273 wprintf(L
"Count: %u\n", Hashtable->Count);
274 wprintf(L
"Allocated buckets: %u\n", Hashtable->AllocatedBuckets);
275 wprintf(L
"Allocated entries: %u\n", Hashtable->AllocatedEntries);
276 wprintf(L
"Next free entry: %d\n", Hashtable->FreeEntry);
277 wprintf(L
"Next usable entry: %d\n", Hashtable->NextEntry);
279 wprintf(L
"Hash function: %s\n", PhpGetSymbolForAddress(Hashtable->HashFunction));
280 wprintf(L
"Compare function: %s\n", PhpGetSymbolForAddress(Hashtable->CompareFunction));
282 wprintf(L
"\nBuckets:\n");
284 for (i = 0; i < Hashtable->AllocatedBuckets; i++)
291 index = Hashtable->Buckets[i];
301 expectedLookupMisses += count - 1;
306 wprintf(L
"%lu: ", i);
310 index = Hashtable->Buckets[i];
314 wprintf(L
"%lu", index);
331 wprintf(L
"\nExpected lookup misses: %lu\n", expectedLookupMisses);
335 static VOID PhpDebugCreateObjectHook(
355 static VOID PhpDeleteNewObjectList(
363 for (i = 0; i < NewObjectList->
Count; i++)
367 NewObjectList = NULL;
372 static BOOLEAN PhpStringHashtableCompareFunction(
383 static ULONG PhpStringHashtableHashFunction(
389 return PhHashBytes((PUCHAR)entry->String->Buffer, entry->String->Length);
392 static int __cdecl PhpStringEntryCompareByCount(
393 _In_
const void *elem1,
394 _In_
const void *elem2
400 return uintptrcmp(entry2->Count, entry1->Count);
403 static NTSTATUS PhpLeakEnumerationRoutine(
405 _In_ PVOID HeapHandle,
406 _In_ PVOID BaseAddress,
407 _In_ SIZE_T BlockSize,
408 _In_ ULONG StackTraceDepth,
409 _In_ PVOID *StackTrace
414 if (!InLeakDetection)
422 wprintf(L
"Leak at 0x%Ix (%Iu bytes). Stack trace:\n", BaseAddress, BlockSize);
424 for (i = 0; i < StackTraceDepth; i++)
428 symbol =
PhGetSymbolFromAddress(DebugConsoleSymbolProvider, (ULONG64)StackTrace[i], NULL, NULL, NULL, NULL);
431 wprintf(L
"\t%s\n", symbol->
Buffer);
438 NumberOfLeaksShown++;
446 typedef struct _STOPWATCH
448 LARGE_INTEGER StartCounter;
449 LARGE_INTEGER EndCounter;
450 LARGE_INTEGER Frequency;
453 static VOID PhInitializeStopwatch(
454 _Out_ PSTOPWATCH Stopwatch
457 Stopwatch->StartCounter.QuadPart = 0;
458 Stopwatch->EndCounter.QuadPart = 0;
461 static VOID PhStartStopwatch(
462 _Inout_ PSTOPWATCH Stopwatch
465 NtQueryPerformanceCounter(&Stopwatch->StartCounter, &Stopwatch->Frequency);
468 static VOID PhStopStopwatch(
469 _Inout_ PSTOPWATCH Stopwatch
472 NtQueryPerformanceCounter(&Stopwatch->EndCounter, NULL);
475 static ULONG PhGetMillisecondsStopwatch(
476 _In_ PSTOPWATCH Stopwatch
479 LARGE_INTEGER countsPerMs;
481 countsPerMs = Stopwatch->Frequency;
482 countsPerMs.QuadPart /= 1000;
484 return (ULONG)((Stopwatch->EndCounter.QuadPart - Stopwatch->StartCounter.QuadPart) /
485 countsPerMs.QuadPart);
492 typedef struct _RW_TEST_CONTEXT
505 static LONG RwReadersActive;
506 static LONG RwWritersActive;
508 static NTSTATUS PhpRwLockTestThreadStart(
512 #define RW_ITERS 10000
513 #define RW_READ_ITERS 100
514 #define RW_WRITE_ITERS 10
515 #define RW_READ_SPIN_ITERS 60
516 #define RW_WRITE_SPIN_ITERS 200
532 context.AcquireShared(context.Parameter);
533 _InterlockedIncrement(&RwReadersActive);
538 if (RwWritersActive != 0)
540 wprintf(L
"[fail]: writers active in read zone!\n");
541 NtWaitForSingleObject(NtCurrentProcess(),
FALSE, NULL);
544 _InterlockedDecrement(&RwReadersActive);
545 context.ReleaseShared(context.Parameter);
549 for (m = 0; m < 10; m++)
552 if (j == RW_READ_ITERS / 2)
558 context.AcquireExclusive(context.Parameter);
559 _InterlockedIncrement(&RwWritersActive);
564 if (RwReadersActive != 0)
566 wprintf(L
"[fail]: readers active in write zone!\n");
567 NtWaitForSingleObject(NtCurrentProcess(),
FALSE, NULL);
570 _InterlockedDecrement(&RwWritersActive);
571 context.ReleaseExclusive(context.Parameter);
577 return STATUS_SUCCESS;
580 static VOID PhpTestRwLock(
581 _In_ PRW_TEST_CONTEXT Context
584 #define RW_PROCESSORS 4
592 Context->AcquireExclusive(Context->Parameter);
593 Context->ReleaseExclusive(Context->Parameter);
594 Context->AcquireShared(Context->Parameter);
595 Context->ReleaseShared(Context->Parameter);
599 PhStartStopwatch(&stopwatch);
601 for (i = 0; i < 2000000; i++)
603 Context->AcquireExclusive(Context->Parameter);
604 Context->ReleaseExclusive(Context->Parameter);
605 Context->AcquireShared(Context->Parameter);
606 Context->ReleaseShared(Context->Parameter);
609 PhStopStopwatch(&stopwatch);
611 wprintf(L
"[null] %s: %ums\n", Context->Name, PhGetMillisecondsStopwatch(&stopwatch));
621 threadHandles[i] =
PhCreateThread(0, PhpRwLockTestThreadStart, Context);
625 PhStartStopwatch(&stopwatch);
626 NtWaitForMultipleObjects(RW_PROCESSORS, threadHandles,
WaitAll,
FALSE, NULL);
627 PhStopStopwatch(&stopwatch);
629 for (i = 0; i < RW_PROCESSORS; i++)
630 NtClose(threadHandles[i]);
632 wprintf(L
"[strs] %s: %ums\n", Context->Name, PhGetMillisecondsStopwatch(&stopwatch));
684 0, PhpLoadCurrentProcessSymbolsCallback, DebugConsoleSymbolProvider);
688 PhDbgCreateObjectHook = PhpDebugCreateObjectHook;
691 wprintf(L
"Press Ctrl+C or type \"exit\" to close the debug console. Type \"help\" for a list of commands.\n");
695 static PWSTR delims = L
" \t";
696 static PWSTR commandDebugOnly = L
"This command is not available on non-debug builds.\n";
705 if (!fgetws(line,
sizeof(line) / 2 - 1, stdin))
712 if (inputLength != 0)
713 line[inputLength - 1] = 0;
716 command = wcstok_s(line, delims, &context);
730 L
"objects [type-name-filter]\n"
731 L
"objtrace object-address\n"
745 L
"enableleakdetect\n"
759 RTL_CRITICAL_SECTION testCriticalSection;
768 PhStartStopwatch(&stopwatch);
770 for (i = 0; i < 10000000; i++)
776 PhStopStopwatch(&stopwatch);
779 wprintf(L
"Referencing: %ums\n", PhGetMillisecondsStopwatch(&stopwatch));
786 PhStartStopwatch(&stopwatch);
788 for (i = 0; i < 10000000; i++)
794 PhStopStopwatch(&stopwatch);
797 wprintf(L
"Critical section: %ums\n", PhGetMillisecondsStopwatch(&stopwatch));
804 PhStartStopwatch(&stopwatch);
806 for (i = 0; i < 10000000; i++)
812 PhStopStopwatch(&stopwatch);
815 wprintf(L
"Fast lock: %ums\n", PhGetMillisecondsStopwatch(&stopwatch));
822 PhStartStopwatch(&stopwatch);
824 for (i = 0; i < 10000000; i++)
830 PhStopStopwatch(&stopwatch);
832 wprintf(L
"Queued lock: %ums\n", PhGetMillisecondsStopwatch(&stopwatch));
839 RTL_CRITICAL_SECTION criticalSection;
841 testContext.Name = L
"FastLock";
846 testContext.Parameter = &fastLock;
848 PhpTestRwLock(&testContext);
851 testContext.Name = L
"QueuedLock";
856 testContext.Parameter = &queuedLock;
858 PhpTestRwLock(&testContext);
860 testContext.Name = L
"CriticalSection";
865 testContext.Parameter = &criticalSection;
867 PhpTestRwLock(&testContext);
870 testContext.Name = L
"QueuedLockMutex";
875 testContext.Parameter = &queuedLock;
877 PhpTestRwLock(&testContext);
882 wprintf(L
"Object small free list count: %u\n", PhObjectSmallFreeList.
Count);
883 wprintf(L
"Statistics:\n");
884 #define PRINT_STATISTIC(Name) wprintf(L#Name L": %u\n", PhLibStatisticsBlock.Name);
886 PRINT_STATISTIC(BaseThreadsCreated);
887 PRINT_STATISTIC(BaseThreadsCreateFailed);
888 PRINT_STATISTIC(BaseStringBuildersCreated);
889 PRINT_STATISTIC(BaseStringBuildersResized);
890 PRINT_STATISTIC(RefObjectsCreated);
891 PRINT_STATISTIC(RefObjectsDestroyed);
892 PRINT_STATISTIC(RefObjectsAllocated);
893 PRINT_STATISTIC(RefObjectsFreed);
894 PRINT_STATISTIC(RefObjectsAllocatedFromSmallFreeList);
895 PRINT_STATISTIC(RefObjectsFreedToSmallFreeList);
896 PRINT_STATISTIC(RefObjectsAllocatedFromTypeFreeList);
897 PRINT_STATISTIC(RefObjectsFreedToTypeFreeList);
898 PRINT_STATISTIC(RefObjectsDeleteDeferred);
899 PRINT_STATISTIC(RefAutoPoolsCreated);
900 PRINT_STATISTIC(RefAutoPoolsDestroyed);
901 PRINT_STATISTIC(RefAutoPoolsDynamicAllocated);
902 PRINT_STATISTIC(RefAutoPoolsDynamicResized);
903 PRINT_STATISTIC(QlBlockSpins);
904 PRINT_STATISTIC(QlBlockWaits);
905 PRINT_STATISTIC(QlAcquireExclusiveBlocks);
906 PRINT_STATISTIC(QlAcquireSharedBlocks);
907 PRINT_STATISTIC(WqWorkQueueThreadsCreated);
908 PRINT_STATISTIC(WqWorkQueueThreadsCreateFailed);
909 PRINT_STATISTIC(WqWorkItemsQueued);
912 wprintf(commandDebugOnly);
918 PWSTR typeFilter = wcstok_s(NULL, delims, &context);
919 PLIST_ENTRY currentEntry;
920 ULONG totalNumberOfObjects = 0;
928 currentEntry = PhDbgObjectListHead.Flink;
930 while (currentEntry != &PhDbgObjectListHead)
935 objectHeader = CONTAINING_RECORD(currentEntry,
PH_OBJECT_HEADER, ObjectListEntry);
940 currentEntry = currentEntry->Flink;
944 totalNumberOfObjects++;
955 (typeFilter && wcsstr(typeName, typeFilter))
958 PhpPrintObjectInfo(objectHeader, 1);
961 currentEntry = currentEntry->Flink;
968 wprintf(L
"Total number: %lu\n", totalNumberOfObjects);
971 wprintf(L
"Total overhead (header): %s\n",
976 wprintf(commandDebugOnly);
982 PWSTR objectAddress = wcstok_s(NULL, delims, &context);
988 wprintf(L
"Missing object address.\n");
997 PVOID stackBackTrace[16];
1003 memcpy(stackBackTrace, objectHeader->StackBackTrace, 16 *
sizeof(PVOID));
1005 __except (EXCEPTION_EXECUTE_HANDLER)
1016 for (i = 0; i < 16; i++)
1018 if (!stackBackTrace[i])
1021 wprintf(L
"%s\n", PhpGetSymbolForAddress(stackBackTrace[i]));
1026 wprintf(L
"Invalid object address.\n");
1029 wprintf(commandDebugOnly);
1035 PLIST_ENTRY currentEntry;
1037 if (ObjectListSnapshot)
1040 ObjectListSnapshot = NULL;
1047 currentEntry = PhDbgObjectListHead.Flink;
1049 while (currentEntry != &PhDbgObjectListHead)
1053 objectHeader = CONTAINING_RECORD(currentEntry,
PH_OBJECT_HEADER, ObjectListEntry);
1054 currentEntry = currentEntry->Flink;
1062 wprintf(commandDebugOnly);
1068 PLIST_ENTRY currentEntry;
1072 if (!ObjectListSnapshot)
1074 wprintf(L
"No snapshot.\n");
1082 currentEntry = PhDbgObjectListHead.Flink;
1084 while (currentEntry != &PhDbgObjectListHead)
1088 objectHeader = CONTAINING_RECORD(currentEntry,
PH_OBJECT_HEADER, ObjectListEntry);
1089 currentEntry = currentEntry->Flink;
1106 for (i = 0; i < newObjects->
Count; i++)
1110 PhpPrintObjectInfo(objectHeader, 1);
1117 wprintf(commandDebugOnly);
1124 PhpDeleteNewObjectList();
1131 wprintf(commandDebugOnly);
1138 PhpDeleteNewObjectList();
1141 wprintf(commandDebugOnly);
1153 wprintf(L
"Object creation hooking not active.\n");
1158 for (i = 0; i < NewObjectList->
Count; i++)
1165 wprintf(commandDebugOnly);
1170 PWSTR addressString = wcstok_s(NULL, delims, &context);
1186 PWSTR addressString = wcstok_s(NULL, delims, &context);
1202 wprintf(L
"Static count: %u\n", userAutoPool->
StaticCount);
1203 wprintf(L
"Dynamic count: %u\n", userAutoPool->
DynamicCount);
1206 wprintf(L
"Static objects:\n");
1211 wprintf(L
"Dynamic objects:\n");
1216 __except (EXCEPTION_EXECUTE_HANDLER)
1225 PLIST_ENTRY currentEntry;
1229 currentEntry = PhDbgThreadListHead.Flink;
1231 while (currentEntry != &PhDbgThreadListHead)
1233 PPHP_BASE_THREAD_DBG dbg;
1235 dbg = CONTAINING_RECORD(currentEntry, PHP_BASE_THREAD_DBG, ListEntry);
1237 wprintf(L
"Thread %u\n", (ULONG)dbg->ClientId.UniqueThread);
1238 wprintf(L
"\tStart Address: %s\n", PhpGetSymbolForAddress(dbg->StartAddress));
1239 wprintf(L
"\tParameter: %Ix\n", dbg->Parameter);
1240 wprintf(L
"\tCurrent auto-pool: %Ix\n", dbg->CurrentAutoPool);
1242 currentEntry = currentEntry->Flink;
1247 wprintf(commandDebugOnly);
1255 if (PhDbgProviderList)
1259 for (i = 0; i < PhDbgProviderList->Count; i++)
1263 PLIST_ENTRY providerEntry;
1272 wprintf(L
"Thread not running\n");
1277 providerEntry = providerThread->
ListHead.Flink;
1279 while (providerEntry != &providerThread->
ListHead)
1285 wprintf(L
"\tProvider registration at %Ix\n", registration);
1286 wprintf(L
"\t\tEnabled: %s\n", registration->
Enabled ? L
"Yes" : L
"No");
1287 wprintf(L
"\t\tFunction: %s\n", PhpGetSymbolForAddress(registration->
Function));
1289 if (registration->
Object)
1291 wprintf(L
"\t\tObject:\n");
1295 providerEntry = providerEntry->Flink;
1306 wprintf(commandDebugOnly);
1314 if (PhDbgWorkQueueList)
1318 for (i = 0; i < PhDbgWorkQueueList->Count; i++)
1321 PLIST_ENTRY workQueueItemEntry;
1323 wprintf(L
"Work queue at %s\n", PhpGetSymbolForAddress(workQueue));
1326 wprintf(L
"No work timeout: %d\n", workQueue->
NoWorkTimeout);
1329 wprintf(L
"Busy count: %u\n", workQueue->
BusyCount);
1340 workQueueItem = CONTAINING_RECORD(workQueueItemEntry,
PH_WORK_QUEUE_ITEM, ListEntry);
1342 wprintf(L
"\tWork queue item at %Ix\n", workQueueItem);
1343 wprintf(L
"\t\tFunction: %s\n", PhpGetSymbolForAddress(workQueueItem->
Function));
1344 wprintf(L
"\t\tContext: %Ix\n", workQueueItem->
Context);
1346 workQueueItemEntry = workQueueItemEntry->Blink;
1357 wprintf(commandDebugOnly);
1364 SYSTEMTIME systemTime;
1374 wprintf(L
"Records for %s %s:\n",
1379 startRecord = record;
1389 }
while (record != startRecord);
1399 LONG_PTR processIdFilter = -9;
1400 ULONG_PTR processAddressFilter = 0;
1401 PWSTR imageNameFilter = NULL;
1402 BOOLEAN showAll =
FALSE;
1404 ULONG numberOfProcesses;
1407 filterString = wcstok_s(NULL, delims, &context);
1414 processIdFilter = (LONG_PTR)filter64;
1416 processAddressFilter = (ULONG_PTR)filter64;
1418 imageNameFilter = filterString;
1427 for (i = 0; i < numberOfProcesses; i++)
1433 (processIdFilter != -9 && (LONG_PTR)process->
ProcessId == processIdFilter) ||
1434 (processAddressFilter != 0 && (ULONG_PTR)process == processAddressFilter) ||
1439 wprintf(L
"\tRecord at %Ix\n", process->
Record);
1440 wprintf(L
"\tQuery handle %Ix\n", process->
QueryHandle);
1443 wprintf(L
"\tFlags: %u\n", process->
Flags);
1453 PLIST_ENTRY currentEntry;
1457 ULONG enumerationKey;
1462 PhpStringHashtableCompareFunction,
1463 PhpStringHashtableHashFunction,
1469 currentEntry = PhDbgObjectListHead.Flink;
1471 while (currentEntry != &PhDbgObjectListHead)
1478 objectHeader = CONTAINING_RECORD(currentEntry,
PH_OBJECT_HEADER, ObjectListEntry);
1479 currentEntry = currentEntry->Flink;
1490 localStringEntry.String = string;
1495 stringEntry->Count = 1;
1500 stringEntry->Count++;
1523 for (i = 0; i < 40 && i < list->
Count; i++)
1525 stringEntry = list->
Items[i];
1526 wprintf(L
"%Iu\t%.64s\n", stringEntry->Count, stringEntry->String->Buffer);
1529 wprintf(L
"\nTotal unique strings: %u\n", list->
Count);
1533 for (i = 0; i < list->
Count; i++)
1535 stringEntry = list->
Items[i];
1542 wprintf(commandDebugOnly);
1555 wprintf(L
"Unable to initialize heap debugging. Make sure that you are using Windows 7 or above.");
1560 VOID (NTAPI *rtlDetectHeapLeaks)(
VOID);
1561 PWSTR options = wcstok_s(NULL, delims, &context);
1565 if (!(NtCurrentPeb()->NtGlobalFlag & FLG_USER_STACK_TRACE_DB))
1567 wprintf(L
"Warning: user-mode stack trace database is not enabled. Stack traces will not be displayed.\n");
1570 ShowAllLeaks =
FALSE;
1575 ShowAllLeaks =
TRUE;
1578 if (rtlDetectHeapLeaks)
1580 InLeakDetection =
TRUE;
1582 NumberOfLeaksShown = 0;
1583 rtlDetectHeapLeaks();
1584 InLeakDetection =
FALSE;
1586 wprintf(L
"\nNumber of leaks: %lu (%lu displayed)\n", NumberOfLeaks, NumberOfLeaksShown);
1591 PWSTR addressString;
1596 ULONG64 numberOfBytes64;
1598 ULONG numberOfBytes;
1603 addressString = wcstok_s(NULL, delims, &context);
1608 bytesString = wcstok_s(NULL, delims, &context);
1612 bytesString = L
"16";
1620 address = (PUCHAR)address64;
1621 numberOfBytes = (ULONG)numberOfBytes64;
1623 if (numberOfBytes > 256)
1625 wprintf(L
"Number of bytes must be 256 or smaller.\n");
1629 blockSize =
sizeof(buffer);
1631 while (numberOfBytes != 0)
1633 if (blockSize > numberOfBytes)
1634 blockSize = numberOfBytes;
1638 memcpy(buffer, address, blockSize);
1640 __except (EXCEPTION_EXECUTE_HANDLER)
1642 wprintf(L
"Error reading address near %Ix.\n", address);
1647 for (i = 0; i < blockSize; i++)
1648 wprintf(L
"%02x ", buffer[i]);
1651 for (; i <
sizeof(buffer); i++)
1657 for (i = 0; i < blockSize; i++)
1658 putwchar((ULONG)(buffer[i] -
' ') <= (ULONG)(
'~' -
' ') ? buffer[i] :
'.');
1662 address += blockSize;
1663 numberOfBytes -= blockSize;
1669 wprintf(L
"Usage: mem address [numberOfBytes]\n");
1670 wprintf(L
"Example: mem 12345678 16\n");
1674 wprintf(L
"Unrecognized command.\n");
1686 return STATUS_SUCCESS;