Process Hacker
procprv.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * process provider
4  *
5  * Copyright (C) 2009-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * This provider module handles the collection of process information and
25  * system-wide statistics. A list of all running processes is kept and
26  * periodically scanned to detect new and terminated processes.
27  *
28  * The retrieval of certain information is delayed in order to improve
29  * performance. This includes things such as file icons, version information,
30  * digital signature verification, and packed executable detection. These
31  * requests are handed to worker threads which then post back information
32  * to a S-list.
33  *
34  * Also contained in this module is the storage of process records, which
35  * contain static information about processes. Unlike process items which are
36  * removed as soon as their corresponding process exits, process records
37  * remain as long as they are needed by the statistics system, and more
38  * specifically the max. CPU and I/O history buffers. In PH 1.x, a new
39  * formatted string was created at each update containing information about
40  * the maximum-usage process within that interval. Here we use a much more
41  * storage-efficient method, where the raw maximum-usage PIDs are stored for
42  * each interval, and the process record list is searched when the name of a
43  * process is needed.
44  *
45  * The process record list is stored as a list of records sorted by process
46  * creation time. If two or more processes have the same creation time, they
47  * are added to a doubly-linked list. This structure allows for fast searching
48  * in the typical scenario where we know the PID of a process and a specific
49  * time in which the process was running. In this case a binary search is used
50  * and then the list is traversed backwards until the process is found. Binary
51  * search is similarly used for insertion and removal.
52  *
53  * On Windows 7 and above, CPU usage can be calculated from cycle time. However,
54  * cycle time cannot be split into kernel/user components, and cycle time is not
55  * available for DPCs and Interrupts separately (only a "system" cycle time).
56  */
57 
58 #include <phapp.h>
59 #include <kphuser.h>
60 #include <extmgri.h>
61 #include <phplug.h>
62 #include <verify.h>
63 #include <winsta.h>
64 
65 #define PROCESS_ID_BUCKETS 64
66 #define PROCESS_ID_TO_BUCKET_INDEX(ProcessId) (((ULONG)(ProcessId) / 4) & (PROCESS_ID_BUCKETS - 1))
67 
68 typedef struct _PH_PROCESS_QUERY_DATA
69 {
70  SLIST_ENTRY ListEntry;
71  ULONG Stage;
72  PPH_PROCESS_ITEM ProcessItem;
74 
75 typedef struct _PH_PROCESS_QUERY_S1_DATA
76 {
77  PH_PROCESS_QUERY_DATA Header;
78 
79  PPH_STRING CommandLine;
80 
81  HICON SmallIcon;
82  HICON LargeIcon;
83  PH_IMAGE_VERSION_INFO VersionInfo;
84 
85  TOKEN_ELEVATION_TYPE ElevationType;
86  BOOLEAN IsElevated;
87  MANDATORY_LEVEL IntegrityLevel;
88  PWSTR IntegrityString;
89 
90  PPH_STRING JobName;
91  BOOLEAN IsInJob;
92  BOOLEAN IsInSignificantJob;
93 
94  HANDLE ConsoleHostProcessId;
95  PPH_STRING PackageFullName;
96 
97  BOOLEAN IsDotNet;
98  BOOLEAN IsPosix;
99  BOOLEAN IsWow64;
100  BOOLEAN IsWow64Valid;
102 
103 typedef struct _PH_PROCESS_QUERY_S2_DATA
104 {
105  PH_PROCESS_QUERY_DATA Header;
106 
107  VERIFY_RESULT VerifyResult;
108  PPH_STRING VerifySignerName;
109 
110  BOOLEAN IsPacked;
111  ULONG ImportFunctions;
112  ULONG ImportModules;
114 
115 typedef struct _PH_VERIFY_CACHE_ENTRY
116 {
117  PH_AVL_LINKS Links;
118 
119  PPH_STRING FileName;
120  VERIFY_RESULT VerifyResult;
121  PPH_STRING VerifySignerName;
123 
125  _In_ PVOID Object,
126  _In_ ULONG Flags
127  );
128 
130  _In_ PPH_AVL_LINKS Links1,
131  _In_ PPH_AVL_LINKS Links2
132  );
133 
135  _In_ PPH_PROCESS_ITEM ProcessItem
136  );
137 
139  _In_ PPH_PROCESS_ITEM ProcessItem
140  );
141 
143  _In_ PPH_PROCESS_ITEM ProcessItem
144  );
145 
147  _Inout_ PPH_PROCESS_RECORD ProcessRecord
148  );
149 
151  _Inout_ PPH_PROCESS_RECORD ProcessRecord
152  );
153 
155 
159 
161 
162 PHAPPAPI PH_CALLBACK_DECLARE(PhProcessAddedEvent);
163 PHAPPAPI PH_CALLBACK_DECLARE(PhProcessModifiedEvent);
164 PHAPPAPI PH_CALLBACK_DECLARE(PhProcessRemovedEvent);
165 PHAPPAPI PH_CALLBACK_DECLARE(PhProcessesUpdatedEvent);
166 
169 
174 
175 PVOID PhProcessInformation; // only can be used if running on same thread as process provider
182 
185 
186 ULONG64 PhCpuTotalCycleDelta; // real cycle time delta for this period
187 PLARGE_INTEGER PhCpuIdleCycleTime; // cycle time for Idle
188 PLARGE_INTEGER PhCpuSystemCycleTime; // cycle time for DPCs and Interrupts
191 //PPH_UINT64_DELTA PhCpusIdleCycleDelta;
192 
197 
201 
205 
209 
210 static BOOLEAN PhProcessStatisticsInitialized = FALSE;
211 static ULONG PhTimeSequenceNumber = 0;
212 static PH_CIRCULAR_BUFFER_ULONG PhTimeHistory;
213 
214 PH_CIRCULAR_BUFFER_FLOAT PhCpuKernelHistory;
215 PH_CIRCULAR_BUFFER_FLOAT PhCpuUserHistory;
216 //PH_CIRCULAR_BUFFER_FLOAT PhCpuOtherHistory;
217 
218 PPH_CIRCULAR_BUFFER_FLOAT PhCpusKernelHistory;
219 PPH_CIRCULAR_BUFFER_FLOAT PhCpusUserHistory;
220 //PPH_CIRCULAR_BUFFER_FLOAT PhCpusOtherHistory;
221 
222 PH_CIRCULAR_BUFFER_ULONG64 PhIoReadHistory;
223 PH_CIRCULAR_BUFFER_ULONG64 PhIoWriteHistory;
224 PH_CIRCULAR_BUFFER_ULONG64 PhIoOtherHistory;
225 
226 PH_CIRCULAR_BUFFER_ULONG PhCommitHistory;
227 PH_CIRCULAR_BUFFER_ULONG PhPhysicalHistory;
228 
229 PH_CIRCULAR_BUFFER_ULONG PhMaxCpuHistory; // ID of max. CPU process
230 PH_CIRCULAR_BUFFER_ULONG PhMaxIoHistory; // ID of max. I/O process
231 #ifdef PH_RECORD_MAX_USAGE
232 PH_CIRCULAR_BUFFER_FLOAT PhMaxCpuUsageHistory;
233 PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoReadOtherHistory;
234 PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoWriteHistory;
235 #endif
236 
237 static PTS_ALL_PROCESSES_INFO PhpTsProcesses = NULL;
238 static ULONG PhpTsNumberOfProcesses;
239 
240 #ifdef PH_ENABLE_VERIFY_CACHE
242 static PH_QUEUED_LOCK PhpVerifyCacheLock = PH_QUEUED_LOCK_INIT;
243 #endif
244 
246  VOID
247  )
248 {
249  PFLOAT usageBuffer;
250  PPH_UINT64_DELTA deltaBuffer;
251  PPH_CIRCULAR_BUFFER_FLOAT historyBuffer;
252 
253  PhProcessItemType = PhCreateObjectType(L"ProcessItem", 0, PhpProcessItemDeleteProcedure);
254 
255  RtlInitializeSListHead(&PhProcessQueryDataListHead);
256 
257  PhProcessRecordList = PhCreateList(40);
258 
260  &PhDpcsProcessInformation.ImageName,
261  L"DPCs"
262  );
263  PhDpcsProcessInformation.UniqueProcessId = DPCS_PROCESS_ID;
264  PhDpcsProcessInformation.InheritedFromUniqueProcessId = SYSTEM_IDLE_PROCESS_ID;
265 
267  &PhInterruptsProcessInformation.ImageName,
268  L"Interrupts"
269  );
270  PhInterruptsProcessInformation.UniqueProcessId = INTERRUPTS_PROCESS_ID;
271  PhInterruptsProcessInformation.InheritedFromUniqueProcessId = SYSTEM_IDLE_PROCESS_ID;
272 
273  PhCpuInformation = PhAllocate(
276  );
277 
278  PhCpuIdleCycleTime = PhAllocate(
279  sizeof(LARGE_INTEGER) *
281  );
282  PhCpuSystemCycleTime = PhAllocate(
283  sizeof(LARGE_INTEGER) *
285  );
286 
287  usageBuffer = PhAllocate(
288  sizeof(FLOAT) *
290  2
291  );
292  deltaBuffer = PhAllocate(
293  sizeof(PH_UINT64_DELTA) *
295  3 // 4 for PhCpusIdleCycleDelta
296  );
297  historyBuffer = PhAllocate(
298  sizeof(PH_CIRCULAR_BUFFER_FLOAT) *
300  2
301  );
302 
303  PhCpusKernelUsage = usageBuffer;
305 
306  PhCpusKernelDelta = deltaBuffer;
307  PhCpusUserDelta = PhCpusKernelDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors;
308  PhCpusIdleDelta = PhCpusUserDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors;
309  //PhCpusIdleCycleDelta = PhCpusIdleDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors;
310 
311  PhCpusKernelHistory = historyBuffer;
313 
314  memset(deltaBuffer, 0, sizeof(PH_UINT64_DELTA) * (ULONG)PhSystemBasicInformation.NumberOfProcessors);
315 
316  return TRUE;
317 }
318 
320  _In_ PCLIENT_ID ClientId
321  )
322 {
323  PPH_STRING name;
324  PPH_PROCESS_ITEM processItem;
325 
326  processItem = PhReferenceProcessItem(ClientId->UniqueProcess);
327 
328  if (processItem)
329  {
330  name = PhGetClientIdNameEx(ClientId, processItem->ProcessName);
331  PhDereferenceObject(processItem);
332  }
333  else
334  {
335  name = PhGetClientIdNameEx(ClientId, NULL);
336  }
337 
338  return name;
339 }
340 
342  _In_ PCLIENT_ID ClientId,
343  _In_opt_ PPH_STRING ProcessName
344  )
345 {
346  PPH_STRING name;
347  PH_FORMAT format[5];
348 
349  if (ClientId->UniqueThread)
350  {
351  if (ProcessName)
352  {
353  PhInitFormatSR(&format[0], ProcessName->sr);
354  PhInitFormatS(&format[1], L" (");
355  PhInitFormatIU(&format[2], (ULONG_PTR)ClientId->UniqueProcess);
356  PhInitFormatS(&format[3], L"): ");
357  PhInitFormatIU(&format[4], (ULONG_PTR)ClientId->UniqueThread);
358 
359  name = PhFormat(format, 5, ProcessName->Length + 16 * sizeof(WCHAR));
360  }
361  else
362  {
363  PhInitFormatS(&format[0], L"Non-existent process (");
364  PhInitFormatIU(&format[1], (ULONG_PTR)ClientId->UniqueProcess);
365  PhInitFormatS(&format[2], L"): ");
366  PhInitFormatIU(&format[3], (ULONG_PTR)ClientId->UniqueThread);
367 
368  name = PhFormat(format, 4, 0);
369  }
370  }
371  else
372  {
373  if (ProcessName)
374  {
375  PhInitFormatSR(&format[0], ProcessName->sr);
376  PhInitFormatS(&format[1], L" (");
377  PhInitFormatIU(&format[2], (ULONG_PTR)ClientId->UniqueProcess);
378  PhInitFormatC(&format[3], ')');
379 
380  name = PhFormat(format, 4, 0);
381  }
382  else
383  {
384  PhInitFormatS(&format[0], L"Non-existent process (");
385  PhInitFormatIU(&format[1], (ULONG_PTR)ClientId->UniqueProcess);
386  PhInitFormatC(&format[2], ')');
387 
388  name = PhFormat(format, 3, 0);
389  }
390  }
391 
392  return name;
393 }
394 
396  _In_ ULONG PriorityClass
397  )
398 {
399  switch (PriorityClass)
400  {
402  return L"Real Time";
404  return L"High";
406  return L"Above Normal";
408  return L"Normal";
410  return L"Below Normal";
412  return L"Idle";
413  default:
414  return L"Unknown";
415  }
416 }
417 
422  _In_ HANDLE ProcessId
423  )
424 {
425  PPH_PROCESS_ITEM processItem;
426 
427  processItem = PhCreateObject(
429  PhProcessItemType
430  );
431  memset(processItem, 0, sizeof(PH_PROCESS_ITEM));
432  PhInitializeEvent(&processItem->Stage1Event);
434 
435  processItem->ProcessId = ProcessId;
436 
437  if (!PH_IS_FAKE_PROCESS_ID(ProcessId))
438  PhPrintUInt32(processItem->ProcessIdString, (ULONG)ProcessId);
439 
440  // Create the statistics buffers.
441  PhInitializeCircularBuffer_FLOAT(&processItem->CpuKernelHistory, PhStatisticsSampleCount);
442  PhInitializeCircularBuffer_FLOAT(&processItem->CpuUserHistory, PhStatisticsSampleCount);
443  PhInitializeCircularBuffer_ULONG64(&processItem->IoReadHistory, PhStatisticsSampleCount);
444  PhInitializeCircularBuffer_ULONG64(&processItem->IoWriteHistory, PhStatisticsSampleCount);
445  PhInitializeCircularBuffer_ULONG64(&processItem->IoOtherHistory, PhStatisticsSampleCount);
446  PhInitializeCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, PhStatisticsSampleCount);
447  //PhInitializeCircularBuffer_SIZE_T(&processItem->WorkingSetHistory, PhStatisticsSampleCount);
448 
450 
451  return processItem;
452 }
453 
455  _In_ PVOID Object,
456  _In_ ULONG Flags
457  )
458 {
459  PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Object;
460  ULONG i;
461 
463 
464  PhDeleteCircularBuffer_FLOAT(&processItem->CpuKernelHistory);
465  PhDeleteCircularBuffer_FLOAT(&processItem->CpuUserHistory);
466  PhDeleteCircularBuffer_ULONG64(&processItem->IoReadHistory);
467  PhDeleteCircularBuffer_ULONG64(&processItem->IoWriteHistory);
468  PhDeleteCircularBuffer_ULONG64(&processItem->IoOtherHistory);
469  PhDeleteCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory);
470  //PhDeleteCircularBuffer_SIZE_T(&processItem->WorkingSetHistory);
471 
472  if (processItem->ServiceList)
473  {
474  PPH_SERVICE_ITEM serviceItem;
475 
476  i = 0;
477 
478  while (PhEnumPointerList(processItem->ServiceList, &i, &serviceItem))
479  PhDereferenceObject(serviceItem);
480 
481  PhDereferenceObject(processItem->ServiceList);
482  }
483 
484  if (processItem->ProcessName) PhDereferenceObject(processItem->ProcessName);
485  if (processItem->FileName) PhDereferenceObject(processItem->FileName);
486  if (processItem->CommandLine) PhDereferenceObject(processItem->CommandLine);
487  if (processItem->SmallIcon) DestroyIcon(processItem->SmallIcon);
488  if (processItem->LargeIcon) DestroyIcon(processItem->LargeIcon);
489  PhDeleteImageVersionInfo(&processItem->VersionInfo);
490  if (processItem->UserName) PhDereferenceObject(processItem->UserName);
491  if (processItem->JobName) PhDereferenceObject(processItem->JobName);
492  if (processItem->VerifySignerName) PhDereferenceObject(processItem->VerifySignerName);
493  if (processItem->PackageFullName) PhDereferenceObject(processItem->PackageFullName);
494 
495  if (processItem->QueryHandle) NtClose(processItem->QueryHandle);
496 
497  if (processItem->Record) PhDereferenceProcessRecord(processItem->Record);
498 }
499 
500 FORCEINLINE BOOLEAN PhCompareProcessItem(
501  _In_ PPH_PROCESS_ITEM Value1,
502  _In_ PPH_PROCESS_ITEM Value2
503  )
504 {
505  return Value1->ProcessId == Value2->ProcessId;
506 }
507 
508 FORCEINLINE ULONG PhHashProcessItem(
509  _In_ PPH_PROCESS_ITEM Value
510  )
511 {
512  return (ULONG)Value->ProcessId / 4;
513 }
514 
525  _In_ HANDLE ProcessId
526  )
527 {
528  PH_PROCESS_ITEM lookupProcessItem;
529  PPH_HASH_ENTRY entry;
530  PPH_PROCESS_ITEM processItem;
531 
532  lookupProcessItem.ProcessId = ProcessId;
533  entry = PhFindEntryHashSet(
534  PhProcessHashSet,
535  PH_HASH_SET_SIZE(PhProcessHashSet),
536  PhHashProcessItem(&lookupProcessItem)
537  );
538 
539  for (; entry; entry = entry->Next)
540  {
541  processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry);
542 
543  if (PhCompareProcessItem(&lookupProcessItem, processItem))
544  return processItem;
545  }
546 
547  return NULL;
548 }
549 
558  _In_ HANDLE ProcessId
559  )
560 {
561  PPH_PROCESS_ITEM processItem;
562 
563  PhAcquireQueuedLockShared(&PhProcessHashSetLock);
564 
565  processItem = PhpLookupProcessItem(ProcessId);
566 
567  if (processItem)
568  PhReferenceObject(processItem);
569 
570  PhReleaseQueuedLockShared(&PhProcessHashSetLock);
571 
572  return processItem;
573 }
574 
585  _Out_opt_ PPH_PROCESS_ITEM **ProcessItems,
586  _Out_ PULONG NumberOfProcessItems
587  )
588 {
589  PPH_PROCESS_ITEM *processItems;
590  ULONG numberOfProcessItems;
591  ULONG count = 0;
592  ULONG i;
593  PPH_HASH_ENTRY entry;
594  PPH_PROCESS_ITEM processItem;
595 
596  if (!ProcessItems)
597  {
598  *NumberOfProcessItems = PhProcessHashSetCount;
599  return;
600  }
601 
602  PhAcquireQueuedLockShared(&PhProcessHashSetLock);
603 
604  numberOfProcessItems = PhProcessHashSetCount;
605  processItems = PhAllocate(sizeof(PPH_PROCESS_ITEM) * numberOfProcessItems);
606 
607  for (i = 0; i < PH_HASH_SET_SIZE(PhProcessHashSet); i++)
608  {
609  for (entry = PhProcessHashSet[i]; entry; entry = entry->Next)
610  {
611  processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry);
612  PhReferenceObject(processItem);
613  processItems[count++] = processItem;
614  }
615  }
616 
617  PhReleaseQueuedLockShared(&PhProcessHashSetLock);
618 
619  *ProcessItems = processItems;
620  *NumberOfProcessItems = numberOfProcessItems;
621 }
622 
624  _In_ _Assume_refs_(1) PPH_PROCESS_ITEM ProcessItem
625  )
626 {
628  PhProcessHashSet,
629  PH_HASH_SET_SIZE(PhProcessHashSet),
630  &ProcessItem->HashEntry,
631  PhHashProcessItem(ProcessItem)
632  );
634 }
635 
637  _In_ PPH_PROCESS_ITEM ProcessItem
638  )
639 {
640  PhRemoveEntryHashSet(PhProcessHashSet, PH_HASH_SET_SIZE(PhProcessHashSet), &ProcessItem->HashEntry);
642  PhDereferenceObject(ProcessItem);
643 }
644 
646  _In_ PPH_AVL_LINKS Links1,
647  _In_ PPH_AVL_LINKS Links2
648  )
649 {
650  PPH_VERIFY_CACHE_ENTRY entry1 = CONTAINING_RECORD(Links1, PH_VERIFY_CACHE_ENTRY, Links);
651  PPH_VERIFY_CACHE_ENTRY entry2 = CONTAINING_RECORD(Links2, PH_VERIFY_CACHE_ENTRY, Links);
652 
653  return PhCompareString(entry1->FileName, entry2->FileName, TRUE);
654 }
655 
657  _In_ PPH_VERIFY_FILE_INFO Information,
658  _In_opt_ PWSTR PackageFullName,
659  _Out_opt_ PPH_STRING *SignerName
660  )
661 {
662  static PH_STRINGREF codeIntegrityFileName = PH_STRINGREF_INIT(L"\\AppxMetadata\\CodeIntegrity.cat");
663 
664  VERIFY_RESULT result;
665  PPH_STRING additionalCatalogFileName = NULL;
666  PCERT_CONTEXT *signatures;
667  ULONG numberOfSignatures;
668 
669  if (PackageFullName)
670  {
671  PACKAGE_ID *packageId;
672  PPH_STRING packagePath;
673 
674  if (packageId = PhPackageIdFromFullName(PackageFullName))
675  {
676  if (packagePath = PhGetPackagePath(packageId))
677  {
678  additionalCatalogFileName = PhConcatStringRef2(&packagePath->sr, &codeIntegrityFileName);
679  PhDereferenceObject(packagePath);
680  }
681 
682  PhFree(packageId);
683  }
684  }
685 
686  if (additionalCatalogFileName)
687  {
688  Information->NumberOfCatalogFileNames = 1;
689  Information->CatalogFileNames = &additionalCatalogFileName->Buffer;
690  }
691 
692  if (!NT_SUCCESS(PhVerifyFileEx(Information, &result, &signatures, &numberOfSignatures)))
693  {
694  result = VrNoSignature;
695  signatures = NULL;
696  numberOfSignatures = 0;
697  }
698 
699  if (additionalCatalogFileName)
700  PhDereferenceObject(additionalCatalogFileName);
701 
702  if (SignerName)
703  {
704  if (numberOfSignatures != 0)
705  *SignerName = PhGetSignerNameFromCertificate(signatures[0]);
706  else
707  *SignerName = NULL;
708  }
709 
710  PhFreeVerifySignatures(signatures, numberOfSignatures);
711 
712  return result;
713 }
714 
732  _In_ PPH_STRING FileName,
733  _In_opt_ PWSTR PackageFullName,
734  _Out_opt_ PPH_STRING *SignerName,
735  _In_ BOOLEAN CachedOnly
736  )
737 {
738 #ifdef PH_ENABLE_VERIFY_CACHE
739  PPH_AVL_LINKS links;
741  PH_VERIFY_CACHE_ENTRY lookupEntry;
742 
743  lookupEntry.FileName = FileName;
744 
745  PhAcquireQueuedLockShared(&PhpVerifyCacheLock);
746  links = PhFindElementAvlTree(&PhpVerifyCacheSet, &lookupEntry.Links);
747  PhReleaseQueuedLockShared(&PhpVerifyCacheLock);
748 
749  if (links)
750  {
751  entry = CONTAINING_RECORD(links, PH_VERIFY_CACHE_ENTRY, Links);
752 
753  if (SignerName)
754  PhSetReference(SignerName, entry->VerifySignerName);
755 
756  return entry->VerifyResult;
757  }
758  else
759  {
760  VERIFY_RESULT result;
761  PPH_STRING signerName;
762 
763  if (!CachedOnly)
764  {
765  PH_VERIFY_FILE_INFO info;
766 
767  memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO));
768  info.FileName = FileName->Buffer;
770  result = PhVerifyFileWithAdditionalCatalog(&info, PackageFullName, &signerName);
771 
772  if (result != VrTrusted)
773  PhClearReference(&signerName);
774  }
775  else
776  {
777  result = VrUnknown;
778  signerName = NULL;
779  }
780 
781  if (result != VrUnknown)
782  {
783  entry = PhAllocate(sizeof(PH_VERIFY_CACHE_ENTRY));
784  entry->FileName = FileName;
785  entry->VerifyResult = result;
786  entry->VerifySignerName = signerName;
787 
788  PhAcquireQueuedLockExclusive(&PhpVerifyCacheLock);
789  links = PhAddElementAvlTree(&PhpVerifyCacheSet, &entry->Links);
790  PhReleaseQueuedLockExclusive(&PhpVerifyCacheLock);
791 
792  if (!links)
793  {
794  // We successfully added the cache entry. Add references.
795 
796  PhReferenceObject(entry->FileName);
797 
798  if (entry->VerifySignerName)
799  PhReferenceObject(entry->VerifySignerName);
800  }
801  else
802  {
803  // Entry already exists.
804  PhFree(entry);
805  }
806  }
807 
808  if (SignerName)
809  {
810  *SignerName = signerName;
811  }
812  else
813  {
814  if (signerName)
815  PhDereferenceObject(signerName);
816  }
817 
818  return result;
819  }
820 #else
821  VERIFY_RESULT result;
822  PPH_STRING signerName;
823  PH_VERIFY_FILE_INFO info;
824 
825  memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO));
826  info.FileName = FileName->Buffer;
828  result = PhVerifyFileWithAdditionalCatalog(&info, PackageFullName, &signerName);
829 
830  if (result != VrTrusted)
831  PhClearReference(&signerName);
832 
833  if (SignerName)
834  {
835  *SignerName = signerName;
836  }
837  else
838  {
839  if (signerName)
840  PhDereferenceObject(signerName);
841  }
842 
843  return result;
844 #endif
845 }
846 
848  _Inout_ PPH_PROCESS_QUERY_S1_DATA Data
849  )
850 {
851  NTSTATUS status;
852  PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem;
853  HANDLE processId = processItem->ProcessId;
854  HANDLE processHandleLimited = NULL;
855 
856  PhOpenProcess(&processHandleLimited, ProcessQueryAccess, processId);
857 
858  if (processItem->FileName)
859  {
860  // Small icon, large icon.
861  if (ExtractIconEx(
862  processItem->FileName->Buffer,
863  0,
864  &Data->LargeIcon,
865  &Data->SmallIcon,
866  1
867  ) == 0)
868  {
869  Data->LargeIcon = NULL;
870  Data->SmallIcon = NULL;
871  }
872 
873  // Version info.
874  PhInitializeImageVersionInfo(&Data->VersionInfo, processItem->FileName->Buffer);
875  }
876 
877  // Use the default EXE icon if we didn't get the file's icon.
878  {
879  if (!Data->SmallIcon || !Data->LargeIcon)
880  {
881  if (Data->SmallIcon)
882  {
883  DestroyIcon(Data->SmallIcon);
884  Data->SmallIcon = NULL;
885  }
886  else if (Data->LargeIcon)
887  {
888  DestroyIcon(Data->LargeIcon);
889  Data->LargeIcon = NULL;
890  }
891 
892  PhGetStockApplicationIcon(&Data->SmallIcon, &Data->LargeIcon);
893  Data->SmallIcon = DuplicateIcon(NULL, Data->SmallIcon);
894  Data->LargeIcon = DuplicateIcon(NULL, Data->LargeIcon);
895  }
896  }
897 
898 #ifdef _WIN64
899  // WOW64
900  if (processHandleLimited)
901  {
902  if (NT_SUCCESS(PhGetProcessIsWow64(processHandleLimited, &Data->IsWow64)))
903  Data->IsWow64Valid = TRUE;
904  }
905 #else
906  Data->IsWow64Valid = TRUE;
907 #endif
908 
909  // POSIX, command line, .NET
910  {
911  HANDLE processHandle;
912  BOOLEAN queryAccess = FALSE;
913 
914  status = PhOpenProcess(
915  &processHandle,
917  processId
918  );
919 
920  if (!NT_SUCCESS(status) && WindowsVersion >= WINDOWS_8_1)
921  {
922  queryAccess = TRUE;
923  status = PhOpenProcess(
924  &processHandle,
926  processId
927  );
928  }
929 
930  if (NT_SUCCESS(status))
931  {
932  BOOLEAN isPosix = FALSE;
933  BOOLEAN isDotNet = FALSE;
934  PPH_STRING commandLine;
935  ULONG i;
936 
937  if (!queryAccess)
938  {
939  status = PhGetProcessIsPosix(processHandle, &isPosix);
940  Data->IsPosix = isPosix;
941  }
942 
943  if (!NT_SUCCESS(status) || !isPosix)
944  {
945  status = PhGetProcessCommandLine(processHandle, &commandLine);
946 
947  if (NT_SUCCESS(status))
948  {
949  // Some command lines (e.g. from taskeng.exe) have nulls in them.
950  // Since Windows can't display them, we'll replace them with
951  // spaces.
952  for (i = 0; i < (ULONG)commandLine->Length / 2; i++)
953  {
954  if (commandLine->Buffer[i] == 0)
955  commandLine->Buffer[i] = ' ';
956  }
957  }
958  }
959  else
960  {
961  // Get the POSIX command line.
962  status = PhGetProcessPosixCommandLine(processHandle, &commandLine);
963  }
964 
965  if (NT_SUCCESS(status))
966  {
967  Data->CommandLine = commandLine;
968  }
969 
970  if (!queryAccess)
971  {
973  processId,
974  processHandle,
975 #ifdef _WIN64
976  PH_CLR_NO_WOW64_CHECK | (Data->IsWow64 ? PH_CLR_KNOWN_IS_WOW64 : 0),
977 #else
978  0,
979 #endif
980  &isDotNet,
981  NULL
982  );
983  Data->IsDotNet = isDotNet;
984  }
985 
986  NtClose(processHandle);
987  }
988  }
989 
990  // Token information
991  if (processHandleLimited)
992  {
993  if (WINDOWS_HAS_UAC)
994  {
995  HANDLE tokenHandle;
996 
997  status = PhOpenProcessToken(&tokenHandle, TOKEN_QUERY, processHandleLimited);
998 
999  if (NT_SUCCESS(status))
1000  {
1001  // Elevation
1003  tokenHandle,
1004  &Data->ElevationType
1005  )))
1006  {
1007  Data->IsElevated = Data->ElevationType == TokenElevationTypeFull;
1008  }
1009 
1010  // Integrity
1012  tokenHandle,
1013  &Data->IntegrityLevel,
1014  &Data->IntegrityString
1015  );
1016 
1017  NtClose(tokenHandle);
1018  }
1019  }
1020  }
1021 
1022  // Job
1023  if (processHandleLimited)
1024  {
1025  if (KphIsConnected())
1026  {
1027  HANDLE jobHandle = NULL;
1028 
1029  status = KphOpenProcessJob(
1030  processHandleLimited,
1032  &jobHandle
1033  );
1034 
1035  if (NT_SUCCESS(status) && status != STATUS_PROCESS_NOT_IN_JOB && jobHandle)
1036  {
1037  JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimits;
1038 
1039  Data->IsInJob = TRUE;
1040 
1042  NtCurrentProcess(),
1043  jobHandle,
1044  -1,
1045  NULL,
1046  NULL,
1047  NULL,
1048  &Data->JobName
1049  );
1050 
1051  // Process Explorer only recognizes processes as being in jobs if they
1052  // don't have the silent-breakaway-OK limit as their only limit.
1053  // Emulate this behaviour.
1054  if (NT_SUCCESS(PhGetJobBasicLimits(jobHandle, &basicLimits)))
1055  {
1056  Data->IsInSignificantJob =
1057  basicLimits.LimitFlags != JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
1058  }
1059 
1060  NtClose(jobHandle);
1061  }
1062  }
1063  else
1064  {
1065  // KProcessHacker not available. We can determine if the process is
1066  // in a job, but we can't get a handle to the job.
1067 
1068  status = NtIsProcessInJob(processHandleLimited, NULL);
1069 
1070  if (NT_SUCCESS(status))
1071  Data->IsInJob = status == STATUS_PROCESS_IN_JOB;
1072  }
1073  }
1074 
1075  // Console host process
1076  if (processHandleLimited && WINDOWS_HAS_CONSOLE_HOST)
1077  {
1078  PhGetProcessConsoleHostProcessId(processHandleLimited, &Data->ConsoleHostProcessId);
1079  }
1080 
1081  // Package full name
1082  if (processHandleLimited && WINDOWS_HAS_IMMERSIVE)
1083  {
1084  Data->PackageFullName = PhGetProcessPackageFullName(processHandleLimited);
1085  }
1086 
1087  if (processHandleLimited)
1088  NtClose(processHandleLimited);
1089 
1090  PhpQueueProcessQueryStage2(processItem);
1091 }
1092 
1094  _Inout_ PPH_PROCESS_QUERY_S2_DATA Data
1095  )
1096 {
1097  NTSTATUS status;
1098  PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem;
1099 
1100  if (PhEnableProcessQueryStage2 && processItem->FileName)
1101  {
1102  PPH_STRING packageFullName = NULL;
1103 
1104  if (processItem->QueryHandle)
1105  packageFullName = PhGetProcessPackageFullName(processItem->QueryHandle);
1106 
1107  Data->VerifyResult = PhVerifyFileCached(
1108  processItem->FileName,
1109  PhGetString(packageFullName),
1110  &Data->VerifySignerName,
1111  FALSE
1112  );
1113 
1114  if (packageFullName)
1115  PhDereferenceObject(packageFullName);
1116 
1117  status = PhIsExecutablePacked(
1118  processItem->FileName->Buffer,
1119  &Data->IsPacked,
1120  &Data->ImportModules,
1121  &Data->ImportFunctions
1122  );
1123 
1124  // If we got an image-related error, the image is packed.
1125  if (
1126  status == STATUS_INVALID_IMAGE_NOT_MZ ||
1127  status == STATUS_INVALID_IMAGE_FORMAT ||
1128  status == STATUS_ACCESS_VIOLATION
1129  )
1130  {
1131  Data->IsPacked = TRUE;
1132  Data->ImportModules = -1;
1133  Data->ImportFunctions = -1;
1134  }
1135  }
1136 }
1137 
1139  _In_ PVOID Parameter
1140  )
1141 {
1143  PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter;
1144 
1145  data = PhAllocate(sizeof(PH_PROCESS_QUERY_S1_DATA));
1146  memset(data, 0, sizeof(PH_PROCESS_QUERY_S1_DATA));
1147  data->Header.Stage = 1;
1148  data->Header.ProcessItem = processItem;
1149 
1150  PhpProcessQueryStage1(data);
1151 
1152  RtlInterlockedPushEntrySList(&PhProcessQueryDataListHead, &data->Header.ListEntry);
1153 
1154  return STATUS_SUCCESS;
1155 }
1156 
1158  _In_ PVOID Parameter
1159  )
1160 {
1162  PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter;
1163 
1164  data = PhAllocate(sizeof(PH_PROCESS_QUERY_S2_DATA));
1165  memset(data, 0, sizeof(PH_PROCESS_QUERY_S2_DATA));
1166  data->Header.Stage = 2;
1167  data->Header.ProcessItem = processItem;
1168 
1169  PhpProcessQueryStage2(data);
1170 
1171  RtlInterlockedPushEntrySList(&PhProcessQueryDataListHead, &data->Header.ListEntry);
1172 
1173  return STATUS_SUCCESS;
1174 }
1175 
1177  _In_ PPH_PROCESS_ITEM ProcessItem
1178  )
1179 {
1180  // Ref: dereferenced when the provider update function removes the item from
1181  // the queue.
1182  PhReferenceObject(ProcessItem);
1184 }
1185 
1187  _In_ PPH_PROCESS_ITEM ProcessItem
1188  )
1189 {
1191  {
1192  PhReferenceObject(ProcessItem);
1194  }
1195 }
1196 
1198  _In_ PPH_PROCESS_QUERY_S1_DATA Data
1199  )
1200 {
1201  PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem;
1202 
1203  processItem->CommandLine = Data->CommandLine;
1204  processItem->SmallIcon = Data->SmallIcon;
1205  processItem->LargeIcon = Data->LargeIcon;
1206  memcpy(&processItem->VersionInfo, &Data->VersionInfo, sizeof(PH_IMAGE_VERSION_INFO));
1207  processItem->ElevationType = Data->ElevationType;
1208  processItem->IntegrityLevel = Data->IntegrityLevel;
1209  processItem->IntegrityString = Data->IntegrityString;
1210  processItem->JobName = Data->JobName;
1211  processItem->ConsoleHostProcessId = Data->ConsoleHostProcessId;
1212  processItem->PackageFullName = Data->PackageFullName;
1213  processItem->IsDotNet = Data->IsDotNet;
1214  processItem->IsElevated = Data->IsElevated;
1215  processItem->IsInJob = Data->IsInJob;
1216  processItem->IsInSignificantJob = Data->IsInSignificantJob;
1217  processItem->IsPosix = Data->IsPosix;
1218  processItem->IsWow64 = Data->IsWow64;
1219  processItem->IsWow64Valid = Data->IsWow64Valid;
1220 
1221  PhSwapReference(&processItem->Record->CommandLine, processItem->CommandLine);
1222 }
1223 
1225  _In_ PPH_PROCESS_QUERY_S2_DATA Data
1226  )
1227 {
1228  PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem;
1229 
1230  processItem->VerifyResult = Data->VerifyResult;
1231  processItem->VerifySignerName = Data->VerifySignerName;
1232  processItem->IsPacked = Data->IsPacked;
1233  processItem->ImportFunctions = Data->ImportFunctions;
1234  processItem->ImportModules = Data->ImportModules;
1235 }
1236 
1238  _Inout_ PPH_PROCESS_ITEM ProcessItem,
1239  _In_ PSYSTEM_PROCESS_INFORMATION Process
1240  )
1241 {
1242  NTSTATUS status;
1243  HANDLE processHandle = NULL;
1244 
1245  ProcessItem->ParentProcessId = Process->InheritedFromUniqueProcessId;
1246  ProcessItem->SessionId = Process->SessionId;
1247  ProcessItem->CreateTime = Process->CreateTime;
1248 
1249  if (ProcessItem->ProcessId != SYSTEM_IDLE_PROCESS_ID)
1250  ProcessItem->ProcessName = PhCreateStringFromUnicodeString(&Process->ImageName);
1251  else
1252  ProcessItem->ProcessName = PhCreateString(SYSTEM_IDLE_PROCESS_NAME);
1253 
1254  PhPrintUInt32(ProcessItem->ParentProcessIdString, (ULONG)ProcessItem->ParentProcessId);
1255  PhPrintUInt32(ProcessItem->SessionIdString, ProcessItem->SessionId);
1256 
1257  PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessItem->ProcessId);
1258 
1259  // Process information
1260  {
1261  // If we're dealing with System (PID 4), we need to get the
1262  // kernel file name. Otherwise, get the image file name.
1263 
1264  if (ProcessItem->ProcessId != SYSTEM_PROCESS_ID)
1265  {
1266  PPH_STRING fileName;
1267 
1269  {
1270  if (processHandle)
1271  {
1272  PhGetProcessImageFileNameWin32(processHandle, &ProcessItem->FileName);
1273  }
1274  else
1275  {
1276  if (NT_SUCCESS(PhGetProcessImageFileNameByProcessId(ProcessItem->ProcessId, &fileName)))
1277  {
1278  ProcessItem->FileName = PhGetFileName(fileName);
1279  PhDereferenceObject(fileName);
1280  }
1281  }
1282  }
1283  else
1284  {
1285  if (processHandle && NT_SUCCESS(PhGetProcessImageFileName(processHandle, &fileName)))
1286  {
1287  ProcessItem->FileName = PhGetFileName(fileName);
1288  PhDereferenceObject(fileName);
1289  }
1290  }
1291  }
1292  else
1293  {
1294  PPH_STRING fileName;
1295 
1296  fileName = PhGetKernelFileName();
1297 
1298  if (fileName)
1299  {
1300  ProcessItem->FileName = PhGetFileName(fileName);
1301  PhDereferenceObject(fileName);
1302  }
1303  }
1304  }
1305 
1306  // Token-related information
1307  if (
1308  processHandle &&
1309  ProcessItem->ProcessId != SYSTEM_PROCESS_ID // Token of System process can't be opened sometimes
1310  )
1311  {
1312  HANDLE tokenHandle;
1313 
1314  status = PhOpenProcessToken(&tokenHandle, TOKEN_QUERY, processHandle);
1315 
1316  if (NT_SUCCESS(status))
1317  {
1318  // User name
1319  {
1320  PTOKEN_USER user;
1321 
1322  status = PhGetTokenUser(tokenHandle, &user);
1323 
1324  if (NT_SUCCESS(status))
1325  {
1326  ProcessItem->UserName = PhGetSidFullName(user->User.Sid, TRUE, NULL);
1327  PhFree(user);
1328  }
1329  }
1330 
1331  NtClose(tokenHandle);
1332  }
1333  }
1334  else
1335  {
1336  if (
1337  ProcessItem->ProcessId == SYSTEM_IDLE_PROCESS_ID ||
1338  ProcessItem->ProcessId == SYSTEM_PROCESS_ID // System token can't be opened on XP
1339  )
1340  {
1341  PhSetReference(&ProcessItem->UserName, PhLocalSystemName);
1342  }
1343  }
1344 
1345  if (!ProcessItem->UserName && WindowsVersion <= WINDOWS_XP)
1346  {
1347  // In some cases we can get the user SID using WTS (only works on XP and below).
1348 
1349  if (!PhpTsProcesses)
1350  {
1352  NULL,
1353  0,
1354  &PhpTsNumberOfProcesses,
1355  &PhpTsProcesses
1356  );
1357  }
1358 
1359  if (PhpTsProcesses)
1360  {
1361  ULONG i;
1362 
1363  for (i = 0; i < PhpTsNumberOfProcesses; i++)
1364  {
1365  if (UlongToHandle(PhpTsProcesses[i].pTsProcessInfo->UniqueProcessId) == ProcessItem->ProcessId)
1366  {
1367  ProcessItem->UserName = PhGetSidFullName(PhpTsProcesses[i].pSid, TRUE, NULL);
1368  break;
1369  }
1370  }
1371  }
1372  }
1373 
1374  NtClose(processHandle);
1375 }
1376 
1378  _Inout_ PPH_PROCESS_ITEM ProcessItem,
1379  _In_ PSYSTEM_PROCESS_INFORMATION Process
1380  )
1381 {
1382  ProcessItem->BasePriority = Process->BasePriority;
1383 
1384  if (ProcessItem->QueryHandle)
1385  {
1386  PROCESS_PRIORITY_CLASS priorityClass;
1387 
1388  if (NT_SUCCESS(NtQueryInformationProcess(
1389  ProcessItem->QueryHandle,
1390  ProcessPriorityClass,
1391  &priorityClass,
1392  sizeof(PROCESS_PRIORITY_CLASS),
1393  NULL
1394  )))
1395  {
1396  ProcessItem->PriorityClass = priorityClass.PriorityClass;
1397  }
1398  }
1399  else
1400  {
1401  ProcessItem->PriorityClass = 0;
1402  }
1403 
1404  ProcessItem->KernelTime = Process->KernelTime;
1405  ProcessItem->UserTime = Process->UserTime;
1406  ProcessItem->NumberOfHandles = Process->HandleCount;
1407  ProcessItem->NumberOfThreads = Process->NumberOfThreads;
1408  ProcessItem->WorkingSetPrivateSize = (SIZE_T)Process->WorkingSetPrivateSize.QuadPart;
1409  ProcessItem->PeakNumberOfThreads = Process->NumberOfThreadsHighWatermark;
1410  ProcessItem->HardFaultCount = Process->HardFaultCount;
1411 
1412  // Update VM and I/O counters.
1413  ProcessItem->VmCounters = *(PVM_COUNTERS_EX)&Process->PeakVirtualSize;
1414  ProcessItem->IoCounters = *(PIO_COUNTERS)&Process->ReadOperationCount;
1415 }
1416 
1418  VOID
1419  )
1420 {
1421  NtQuerySystemInformation(
1423  &PhPerfInformation,
1425  NULL
1426  );
1427 
1428  PhUpdateDelta(&PhIoReadDelta, PhPerfInformation.IoReadTransferCount.QuadPart);
1429  PhUpdateDelta(&PhIoWriteDelta, PhPerfInformation.IoWriteTransferCount.QuadPart);
1430  PhUpdateDelta(&PhIoOtherDelta, PhPerfInformation.IoOtherTransferCount.QuadPart);
1431 }
1432 
1434  _In_ BOOLEAN SetCpuUsage,
1435  _Out_ PULONG64 TotalTime
1436  )
1437 {
1438  ULONG i;
1439  ULONG64 totalTime;
1440 
1441  NtQuerySystemInformation(
1443  PhCpuInformation,
1445  NULL
1446  );
1447 
1448  // Zero the CPU totals.
1449  memset(&PhCpuTotals, 0, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
1450 
1451  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1452  {
1454  &PhCpuInformation[i];
1455 
1456  // KernelTime includes idle time.
1457  cpuInfo->KernelTime.QuadPart -= cpuInfo->IdleTime.QuadPart;
1458  cpuInfo->KernelTime.QuadPart += cpuInfo->DpcTime.QuadPart + cpuInfo->InterruptTime.QuadPart;
1459 
1460  PhCpuTotals.DpcTime.QuadPart += cpuInfo->DpcTime.QuadPart;
1461  PhCpuTotals.IdleTime.QuadPart += cpuInfo->IdleTime.QuadPart;
1462  PhCpuTotals.InterruptCount += cpuInfo->InterruptCount;
1463  PhCpuTotals.InterruptTime.QuadPart += cpuInfo->InterruptTime.QuadPart;
1464  PhCpuTotals.KernelTime.QuadPart += cpuInfo->KernelTime.QuadPart;
1465  PhCpuTotals.UserTime.QuadPart += cpuInfo->UserTime.QuadPart;
1466 
1467  PhUpdateDelta(&PhCpusKernelDelta[i], cpuInfo->KernelTime.QuadPart);
1468  PhUpdateDelta(&PhCpusUserDelta[i], cpuInfo->UserTime.QuadPart);
1469  PhUpdateDelta(&PhCpusIdleDelta[i], cpuInfo->IdleTime.QuadPart);
1470 
1471  if (SetCpuUsage)
1472  {
1473  totalTime = PhCpusKernelDelta[i].Delta + PhCpusUserDelta[i].Delta + PhCpusIdleDelta[i].Delta;
1474 
1475  if (totalTime != 0)
1476  {
1477  PhCpusKernelUsage[i] = (FLOAT)PhCpusKernelDelta[i].Delta / totalTime;
1478  PhCpusUserUsage[i] = (FLOAT)PhCpusUserDelta[i].Delta / totalTime;
1479  }
1480  else
1481  {
1482  PhCpusKernelUsage[i] = 0;
1483  PhCpusUserUsage[i] = 0;
1484  }
1485  }
1486  }
1487 
1488  PhUpdateDelta(&PhCpuKernelDelta, PhCpuTotals.KernelTime.QuadPart);
1489  PhUpdateDelta(&PhCpuUserDelta, PhCpuTotals.UserTime.QuadPart);
1490  PhUpdateDelta(&PhCpuIdleDelta, PhCpuTotals.IdleTime.QuadPart);
1491 
1492  totalTime = PhCpuKernelDelta.Delta + PhCpuUserDelta.Delta + PhCpuIdleDelta.Delta;
1493 
1494  if (SetCpuUsage)
1495  {
1496  if (totalTime != 0)
1497  {
1498  PhCpuKernelUsage = (FLOAT)PhCpuKernelDelta.Delta / totalTime;
1499  PhCpuUserUsage = (FLOAT)PhCpuUserDelta.Delta / totalTime;
1500  }
1501  else
1502  {
1503  PhCpuKernelUsage = 0;
1504  PhCpuUserUsage = 0;
1505  }
1506  }
1507 
1508  *TotalTime = totalTime;
1509 }
1510 
1512  _Out_ PULONG64 IdleCycleTime
1513  )
1514 {
1515  ULONG i;
1516  ULONG64 total;
1517 
1518  // Idle
1519  // We need to query this separately because the idle cycle time in SYSTEM_PROCESS_INFORMATION
1520  // doesn't give us data for individual processors.
1521 
1522  NtQuerySystemInformation(
1525  sizeof(LARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors,
1526  NULL
1527  );
1528 
1529  total = 0;
1530 
1531  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1532  {
1533  //PhUpdateDelta(&PhCpusIdleCycleDelta[i], PhCpuIdleCycleTime[i].QuadPart);
1534  total += PhCpuIdleCycleTime[i].QuadPart;
1535  }
1536 
1537  PhUpdateDelta(&PhCpuIdleCycleDelta, total);
1538  *IdleCycleTime = PhCpuIdleCycleDelta.Delta;
1539 
1540  // System
1541 
1542  NtQuerySystemInformation(
1545  sizeof(LARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors,
1546  NULL
1547  );
1548 
1549  total = 0;
1550 
1551  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1552  {
1553  total += PhCpuSystemCycleTime[i].QuadPart;
1554  }
1555 
1556  PhUpdateDelta(&PhCpuSystemCycleDelta, total);
1557 }
1558 
1560  _In_ ULONG64 TotalCycleTime,
1561  _In_ ULONG64 IdleCycleTime
1562  )
1563 {
1564  ULONG i;
1565  FLOAT baseCpuUsage;
1566  FLOAT totalTimeDelta;
1567  ULONG64 totalTime;
1568 
1569  // Cycle time is not only lacking for kernel/user components, but also for individual
1570  // processors. We can get the total idle cycle time for individual processors but
1571  // without knowing the total cycle time for individual processors, this information
1572  // is useless.
1573  //
1574  // We'll start by calculating the total CPU usage, then we'll calculate the kernel/user
1575  // components. In the event that the corresponding CPU time deltas are zero, we'll split
1576  // the CPU usage evenly across the kernel/user components. CPU usage for individual
1577  // processors is left untouched, because it's too difficult to provide an estimate.
1578  //
1579  // Let I_1, I_2, ..., I_n be the idle cycle times and T_1, T_2, ..., T_n be the
1580  // total cycle times. Let I'_1, I'_2, ..., I'_n be the idle CPU times and T'_1, T'_2, ...,
1581  // T'_n be the total CPU times.
1582  // We know all I'_n, T'_n and I_n, but we only know sigma(T). The "real" total CPU usage is
1583  // sigma(I)/sigma(T), and the "real" individual CPU usage is I_n/T_n. The problem is that
1584  // we don't know T_n; we only know sigma(T). Hence we need to find values i_1, i_2, ..., i_n
1585  // and t_1, t_2, ..., t_n such that:
1586  // sigma(i)/sigma(t) ~= sigma(I)/sigma(T), and
1587  // i_n/t_n ~= I_n/T_n
1588  //
1589  // Solution 1: Set i_n = I_n and t_n = sigma(T)*T'_n/sigma(T'). Then:
1590  // sigma(i)/sigma(t) = sigma(I)/(sigma(T)*sigma(T')/sigma(T')) = sigma(I)/sigma(T), and
1591  // i_n/t_n = I_n/T'_n*sigma(T')/sigma(T) ~= I_n/T_n since I_n/T'_n ~= I_n/T_n and sigma(T')/sigma(T) ~= 1.
1592  // However, it is not guaranteed that i_n/t_n <= 1, which may lead to CPU usages over 100% being displayed.
1593  //
1594  // Solution 2: Set i_n = I'_n and t_n = T'_n. Then:
1595  // sigma(i)/sigma(t) = sigma(I')/sigma(T') ~= sigma(I)/sigma(T) since I'_n ~= I_n and T'_n ~= T_n.
1596  // i_n/t_n = I'_n/T'_n ~= I_n/T_n as above.
1597  // Not scaling at all is currently the best solution, since it's fast, simple and guarantees that i_n/t_n <= 1.
1598 
1599  baseCpuUsage = 1 - (FLOAT)IdleCycleTime / TotalCycleTime;
1600  totalTimeDelta = (FLOAT)(PhCpuKernelDelta.Delta + PhCpuUserDelta.Delta);
1601 
1602  if (totalTimeDelta != 0)
1603  {
1604  PhCpuKernelUsage = baseCpuUsage * ((FLOAT)PhCpuKernelDelta.Delta / totalTimeDelta);
1605  PhCpuUserUsage = baseCpuUsage * ((FLOAT)PhCpuUserDelta.Delta / totalTimeDelta);
1606  }
1607  else
1608  {
1609  PhCpuKernelUsage = baseCpuUsage / 2;
1610  PhCpuUserUsage = baseCpuUsage / 2;
1611  }
1612 
1613  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1614  {
1615  totalTime = PhCpusKernelDelta[i].Delta + PhCpusUserDelta[i].Delta + PhCpusIdleDelta[i].Delta;
1616 
1617  if (totalTime != 0)
1618  {
1619  PhCpusKernelUsage[i] = (FLOAT)PhCpusKernelDelta[i].Delta / totalTime;
1620  PhCpusUserUsage[i] = (FLOAT)PhCpusUserDelta[i].Delta / totalTime;
1621  }
1622  else
1623  {
1624  PhCpusKernelUsage[i] = 0;
1625  PhCpusUserUsage[i] = 0;
1626  }
1627  }
1628 }
1629 
1631  VOID
1632  )
1633 {
1634  ULONG i;
1635 
1636  PhInitializeCircularBuffer_ULONG(&PhTimeHistory, PhStatisticsSampleCount);
1637  PhInitializeCircularBuffer_FLOAT(&PhCpuKernelHistory, PhStatisticsSampleCount);
1638  PhInitializeCircularBuffer_FLOAT(&PhCpuUserHistory, PhStatisticsSampleCount);
1639  PhInitializeCircularBuffer_ULONG64(&PhIoReadHistory, PhStatisticsSampleCount);
1640  PhInitializeCircularBuffer_ULONG64(&PhIoWriteHistory, PhStatisticsSampleCount);
1641  PhInitializeCircularBuffer_ULONG64(&PhIoOtherHistory, PhStatisticsSampleCount);
1642  PhInitializeCircularBuffer_ULONG(&PhCommitHistory, PhStatisticsSampleCount);
1643  PhInitializeCircularBuffer_ULONG(&PhPhysicalHistory, PhStatisticsSampleCount);
1644  PhInitializeCircularBuffer_ULONG(&PhMaxCpuHistory, PhStatisticsSampleCount);
1645  PhInitializeCircularBuffer_ULONG(&PhMaxIoHistory, PhStatisticsSampleCount);
1646 #ifdef PH_RECORD_MAX_USAGE
1647  PhInitializeCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, PhStatisticsSampleCount);
1648  PhInitializeCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, PhStatisticsSampleCount);
1649  PhInitializeCircularBuffer_ULONG64(&PhMaxIoWriteHistory, PhStatisticsSampleCount);
1650 #endif
1651 
1652  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1653  {
1654  PhInitializeCircularBuffer_FLOAT(&PhCpusKernelHistory[i], PhStatisticsSampleCount);
1655  PhInitializeCircularBuffer_FLOAT(&PhCpusUserHistory[i], PhStatisticsSampleCount);
1656  }
1657 }
1658 
1660  VOID
1661  )
1662 {
1663  ULONG i;
1664  LARGE_INTEGER systemTime;
1665  ULONG secondsSince1980;
1666 
1667  // CPU
1668  PhAddItemCircularBuffer_FLOAT(&PhCpuKernelHistory, PhCpuKernelUsage);
1669  PhAddItemCircularBuffer_FLOAT(&PhCpuUserHistory, PhCpuUserUsage);
1670 
1671  // CPUs
1672  for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++)
1673  {
1674  PhAddItemCircularBuffer_FLOAT(&PhCpusKernelHistory[i], PhCpusKernelUsage[i]);
1675  PhAddItemCircularBuffer_FLOAT(&PhCpusUserHistory[i], PhCpusUserUsage[i]);
1676  }
1677 
1678  // I/O
1679  PhAddItemCircularBuffer_ULONG64(&PhIoReadHistory, PhIoReadDelta.Delta);
1680  PhAddItemCircularBuffer_ULONG64(&PhIoWriteHistory, PhIoWriteDelta.Delta);
1681  PhAddItemCircularBuffer_ULONG64(&PhIoOtherHistory, PhIoOtherDelta.Delta);
1682 
1683  // Memory
1684  PhAddItemCircularBuffer_ULONG(&PhCommitHistory, PhPerfInformation.CommittedPages);
1685  PhAddItemCircularBuffer_ULONG(&PhPhysicalHistory,
1687  );
1688 
1689  // Time
1690  PhQuerySystemTime(&systemTime);
1691  RtlTimeToSecondsSince1980(&systemTime, &secondsSince1980);
1692  PhAddItemCircularBuffer_ULONG(&PhTimeHistory, secondsSince1980);
1693 }
1694 
1708  _In_opt_ PPH_PROCESS_ITEM ProcessItem,
1709  _In_ ULONG Index,
1710  _Out_ PLARGE_INTEGER Time
1711  )
1712 {
1713  ULONG secondsSince1980;
1714  ULONG index;
1715  LARGE_INTEGER time;
1716 
1717  if (ProcessItem)
1718  {
1719  // The sequence number is used to synchronize statistics when a process exits, since
1720  // that process' history is not updated anymore.
1721  index = PhTimeSequenceNumber - ProcessItem->SequenceNumber + Index;
1722 
1723  if (index >= PhTimeHistory.Count)
1724  {
1725  // The data point is too far into the past.
1726  return FALSE;
1727  }
1728  }
1729  else
1730  {
1731  // Assume the index is valid.
1732  index = Index;
1733  }
1734 
1735  secondsSince1980 = PhGetItemCircularBuffer_ULONG(&PhTimeHistory, index);
1736  RtlSecondsSince1980ToTime(secondsSince1980, &time);
1737 
1738  *Time = time;
1739 
1740  return TRUE;
1741 }
1742 
1744  _In_opt_ PPH_PROCESS_ITEM ProcessItem,
1745  _In_ ULONG Index
1746  )
1747 {
1748  LARGE_INTEGER time;
1749  SYSTEMTIME systemTime;
1750 
1751  if (PhGetStatisticsTime(ProcessItem, Index, &time))
1752  {
1753  PhLargeIntegerToLocalSystemTime(&systemTime, &time);
1754 
1755  return PhFormatDateTime(&systemTime);
1756  }
1757  else
1758  {
1759  return PhCreateString(L"Unknown time");
1760  }
1761 }
1762 
1764  _In_ BOOLEAN SendModifiedEvent
1765  )
1766 {
1767  PSLIST_ENTRY entry;
1769  BOOLEAN processed;
1770 
1771  if (!RtlFirstEntrySList(&PhProcessQueryDataListHead))
1772  return;
1773 
1774  entry = RtlInterlockedFlushSList(&PhProcessQueryDataListHead);
1775 
1776  while (entry)
1777  {
1778  data = CONTAINING_RECORD(entry, PH_PROCESS_QUERY_DATA, ListEntry);
1779  entry = entry->Next;
1780  processed = FALSE;
1781 
1782  if (data->Stage == 1)
1783  {
1785  PhSetEvent(&data->ProcessItem->Stage1Event);
1786  processed = TRUE;
1787  }
1788  else if (data->Stage == 2)
1789  {
1791  processed = TRUE;
1792  }
1793 
1794  if (processed)
1795  {
1796  // Invoke the modified event only if the main provider has sent the added event already.
1797  if (SendModifiedEvent && data->ProcessItem->AddedEventSent)
1798  PhInvokeCallback(&PhProcessModifiedEvent, data->ProcessItem);
1799  else
1800  data->ProcessItem->JustProcessed = 1;
1801  }
1802 
1803  PhDereferenceObject(data->ProcessItem);
1804  PhFree(data);
1805  }
1806 }
1807 
1809  _In_ PSYSTEM_PROCESS_INFORMATION Process,
1810  _Out_opt_ PBOOLEAN IsSuspended,
1811  _Out_opt_ PBOOLEAN IsPartiallySuspended,
1812  _Out_opt_ PULONG ContextSwitches
1813  )
1814 {
1815  ULONG i;
1816  BOOLEAN isSuspended;
1817  BOOLEAN isPartiallySuspended;
1818  ULONG contextSwitches;
1819 
1820  isSuspended = PH_IS_REAL_PROCESS_ID(Process->UniqueProcessId);
1821  isPartiallySuspended = FALSE;
1822  contextSwitches = 0;
1823 
1824  for (i = 0; i < Process->NumberOfThreads; i++)
1825  {
1826  if (Process->Threads[i].ThreadState != Waiting ||
1827  Process->Threads[i].WaitReason != Suspended)
1828  {
1829  isSuspended = FALSE;
1830  }
1831  else
1832  {
1833  isPartiallySuspended = TRUE;
1834  }
1835 
1836  contextSwitches += Process->Threads[i].ContextSwitches;
1837  }
1838 
1839  if (IsSuspended)
1840  *IsSuspended = isSuspended;
1841  if (IsPartiallySuspended)
1842  *IsPartiallySuspended = isPartiallySuspended;
1843  if (ContextSwitches)
1844  *ContextSwitches = contextSwitches;
1845 }
1846 
1848  _In_ PVOID Object
1849  )
1850 {
1851  static ULONG runCount = 0;
1853 
1854  // Note about locking:
1855  // Since this is the only function that is allowed to
1856  // modify the process hashtable, locking is not needed
1857  // for shared accesses. However, exclusive accesses
1858  // need locking.
1859 
1860  PVOID processes;
1862  ULONG bucketIndex;
1863 
1864  BOOLEAN isCycleCpuUsageEnabled = FALSE;
1865 
1866  ULONG64 sysTotalTime; // total time for this update period
1867  ULONG64 sysTotalCycleTime = 0; // total cycle time for this update period
1868  ULONG64 sysIdleCycleTime = 0; // total idle cycle time for this update period
1869  FLOAT maxCpuValue = 0;
1870  PPH_PROCESS_ITEM maxCpuProcessItem = NULL;
1871  ULONG64 maxIoValue = 0;
1872  PPH_PROCESS_ITEM maxIoProcessItem = NULL;
1873 
1874  // Pre-update tasks
1875 
1876  if (runCount % 5 == 0)
1877  {
1879  }
1880 
1881  if (runCount % 512 == 0) // yes, a very long time
1882  {
1885  }
1886 
1887  isCycleCpuUsageEnabled = WindowsVersion >= WINDOWS_7 && PhEnableCycleCpuUsage;
1888 
1889  if (!PhProcessStatisticsInitialized)
1890  {
1892  PhProcessStatisticsInitialized = TRUE;
1893  }
1894 
1896 
1897  if (isCycleCpuUsageEnabled)
1898  {
1899  PhpUpdateCpuInformation(FALSE, &sysTotalTime);
1900  PhpUpdateCpuCycleInformation(&sysIdleCycleTime);
1901  }
1902  else
1903  {
1904  PhpUpdateCpuInformation(TRUE, &sysTotalTime);
1905  }
1906 
1907  if (runCount != 0)
1908  {
1909  PhTimeSequenceNumber++;
1910  }
1911 
1912  // Get the process list.
1913 
1914  PhTotalProcesses = 0;
1915  PhTotalThreads = 0;
1916  PhTotalHandles = 0;
1917 
1918  if (!NT_SUCCESS(PhEnumProcesses(&processes)))
1919  return;
1920 
1921  // Notes on cycle-based CPU usage:
1922  //
1923  // Cycle-based CPU usage is a bit tricky to calculate because we cannot get
1924  // the total number of cycles consumed by all processes since system startup - we
1925  // can only get total number of cycles per process. This means there are two ways
1926  // to calculate the system-wide cycle time delta:
1927  //
1928  // 1. Each update, sum the cycle times of all processes, and calculate the system-wide
1929  // delta from this. Process Explorer seems to do this.
1930  // 2. Each update, calculate the cycle time delta for each individual process, and
1931  // sum these deltas to create the system-wide delta. We use this here.
1932  //
1933  // The first method is simpler but has a problem when a process exits and its cycle time
1934  // is no longer counted in the system-wide total. This may cause the delta to be
1935  // negative and all other calculations to become invalid. Process Explorer simply ignored
1936  // this fact and treated the system-wide delta as unsigned (and therefore huge when negative),
1937  // leading to all CPU usages being displayed as "< 0.01".
1938  //
1939  // The second method is used here, but the adjustments must be done before the main new/modified
1940  // pass. We need take into account new, existing and terminated processes.
1941 
1942  // Create the PID hash set. This contains the process information structures returned by
1943  // PhEnumProcesses, distinct from the process item hash set.
1944  // Note that we use the UniqueProcessKey field as the next node pointer to avoid having to
1945  // allocate extra memory.
1946 
1947  memset(pidBuckets, 0, sizeof(pidBuckets));
1948 
1949  process = PH_FIRST_PROCESS(processes);
1950 
1951  do
1952  {
1953  PhTotalProcesses++;
1954  PhTotalThreads += process->NumberOfThreads;
1955  PhTotalHandles += process->HandleCount;
1956 
1957  if (process->UniqueProcessId == SYSTEM_IDLE_PROCESS_ID)
1958  {
1959  process->CycleTime = PhCpuIdleCycleDelta.Value;
1960  process->KernelTime = PhCpuTotals.IdleTime;
1961  }
1962 
1963  bucketIndex = PROCESS_ID_TO_BUCKET_INDEX(process->UniqueProcessId);
1964  process->UniqueProcessKey = (ULONG_PTR)pidBuckets[bucketIndex];
1965  pidBuckets[bucketIndex] = process;
1966 
1967  if (isCycleCpuUsageEnabled)
1968  {
1969  PPH_PROCESS_ITEM processItem;
1970 
1971  if ((processItem = PhpLookupProcessItem(process->UniqueProcessId)) && processItem->CreateTime.QuadPart == process->CreateTime.QuadPart)
1972  sysTotalCycleTime += process->CycleTime - processItem->CycleTimeDelta.Value; // existing process
1973  else
1974  sysTotalCycleTime += process->CycleTime; // new process
1975  }
1976  } while (process = PH_NEXT_PROCESS(process));
1977 
1978  // Add the fake processes to the PID list.
1979  // On Windows 7 the two fake processes are merged into "Interrupts" since we can only get
1980  // cycle time information both DPCs and Interrupts combined.
1981 
1982  if (isCycleCpuUsageEnabled)
1983  {
1984  PhInterruptsProcessInformation.KernelTime.QuadPart = PhCpuTotals.DpcTime.QuadPart + PhCpuTotals.InterruptTime.QuadPart;
1985  PhInterruptsProcessInformation.CycleTime = PhCpuSystemCycleDelta.Value;
1986  sysTotalCycleTime += PhCpuSystemCycleDelta.Delta;
1987  }
1988  else
1989  {
1990  PhDpcsProcessInformation.KernelTime = PhCpuTotals.DpcTime;
1991  PhInterruptsProcessInformation.KernelTime = PhCpuTotals.InterruptTime;
1992  }
1993 
1994  // Look for dead processes.
1995  {
1996  PPH_LIST processesToRemove = NULL;
1997  ULONG i;
1998  PPH_HASH_ENTRY entry;
1999  PPH_PROCESS_ITEM processItem;
2000  PSYSTEM_PROCESS_INFORMATION processEntry;
2001 
2002  for (i = 0; i < PH_HASH_SET_SIZE(PhProcessHashSet); i++)
2003  {
2004  for (entry = PhProcessHashSet[i]; entry; entry = entry->Next)
2005  {
2006  processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry);
2007 
2008  // Check if the process still exists. Note that we take into account PID re-use by
2009  // checking CreateTime as well.
2010 
2011  if (processItem->ProcessId == DPCS_PROCESS_ID)
2012  {
2013  processEntry = &PhDpcsProcessInformation;
2014  }
2015  else if (processItem->ProcessId == INTERRUPTS_PROCESS_ID)
2016  {
2017  processEntry = &PhInterruptsProcessInformation;
2018  }
2019  else
2020  {
2021  processEntry = pidBuckets[PROCESS_ID_TO_BUCKET_INDEX(processItem->ProcessId)];
2022 
2023  while (processEntry && processEntry->UniqueProcessId != processItem->ProcessId)
2024  processEntry = (PSYSTEM_PROCESS_INFORMATION)processEntry->UniqueProcessKey;
2025  }
2026 
2027  if (!processEntry || processEntry->CreateTime.QuadPart != processItem->CreateTime.QuadPart)
2028  {
2029  LARGE_INTEGER exitTime;
2030 
2031  processItem->State |= PH_PROCESS_ITEM_REMOVED;
2032  exitTime.QuadPart = 0;
2033 
2034  if (processItem->QueryHandle)
2035  {
2036  KERNEL_USER_TIMES times;
2037  ULONG64 finalCycleTime;
2038 
2039  if (NT_SUCCESS(PhGetProcessTimes(processItem->QueryHandle, &times)))
2040  {
2041  exitTime = times.ExitTime;
2042  }
2043 
2044  if (isCycleCpuUsageEnabled)
2045  {
2046  if (NT_SUCCESS(PhGetProcessCycleTime(processItem->QueryHandle, &finalCycleTime)))
2047  {
2048  // Adjust deltas for the terminated process because this doesn't get
2049  // picked up anywhere else.
2050  //
2051  // Note that if we don't have sufficient access to the process, the worst
2052  // that will happen is that the CPU usages of other processes will get
2053  // inflated. (See above; if we were using the first technique, we could
2054  // get negative deltas, which is much worse.)
2055  sysTotalCycleTime += finalCycleTime - processItem->CycleTimeDelta.Value;
2056  }
2057  }
2058  }
2059 
2060  // If we don't have a valid exit time, use the current time.
2061  if (exitTime.QuadPart == 0)
2062  PhQuerySystemTime(&exitTime);
2063 
2064  processItem->Record->Flags |= PH_PROCESS_RECORD_DEAD;
2065  processItem->Record->ExitTime = exitTime;
2066 
2067  // Raise the process removed event.
2068  PhInvokeCallback(&PhProcessRemovedEvent, processItem);
2069 
2070  if (!processesToRemove)
2071  processesToRemove = PhCreateList(2);
2072 
2073  PhAddItemList(processesToRemove, processItem);
2074  }
2075  }
2076  }
2077 
2078  // Lock only if we have something to do.
2079  if (processesToRemove)
2080  {
2081  PhAcquireQueuedLockExclusive(&PhProcessHashSetLock);
2082 
2083  for (i = 0; i < processesToRemove->Count; i++)
2084  {
2085  PhpRemoveProcessItem((PPH_PROCESS_ITEM)processesToRemove->Items[i]);
2086  }
2087 
2088  PhReleaseQueuedLockExclusive(&PhProcessHashSetLock);
2089  PhDereferenceObject(processesToRemove);
2090  }
2091  }
2092 
2093  // Go through the queued process query data.
2095 
2096  if (sysTotalTime == 0)
2097  sysTotalTime = -1; // max. value
2098  if (sysTotalCycleTime == 0)
2099  sysTotalCycleTime = -1;
2100 
2101  PhCpuTotalCycleDelta = sysTotalCycleTime;
2102 
2103  // Look for new processes and update existing ones.
2104  process = PH_FIRST_PROCESS(processes);
2105 
2106  while (process)
2107  {
2108  PPH_PROCESS_ITEM processItem;
2109 
2110  processItem = PhpLookupProcessItem(process->UniqueProcessId);
2111 
2112  if (!processItem)
2113  {
2114  PPH_PROCESS_RECORD processRecord;
2115  BOOLEAN isSuspended;
2116  BOOLEAN isPartiallySuspended;
2117  ULONG contextSwitches;
2118 
2119  // Create the process item and fill in basic information.
2120  processItem = PhCreateProcessItem(process->UniqueProcessId);
2121  PhpFillProcessItem(processItem, process);
2122  processItem->SequenceNumber = PhTimeSequenceNumber;
2123 
2124  processRecord = PhpCreateProcessRecord(processItem);
2125  PhpAddProcessRecord(processRecord);
2126  processItem->Record = processRecord;
2127 
2128  // Open a handle to the process for later usage.
2129  // Don't try to do this if the process has no threads. On Windows 8.1, processes without threads are
2130  // probably reflected processes which will not terminate if we have a handle open.
2131  if (process->NumberOfThreads != 0)
2132  {
2133  PhOpenProcess(&processItem->QueryHandle, PROCESS_QUERY_INFORMATION, processItem->ProcessId);
2134 
2135  if (WINDOWS_HAS_LIMITED_ACCESS && !processItem->QueryHandle)
2137  }
2138 
2139  PhpGetProcessThreadInformation(process, &isSuspended, &isPartiallySuspended, &contextSwitches);
2140  PhpUpdateDynamicInfoProcessItem(processItem, process);
2141 
2142  // Initialize the deltas.
2143  PhUpdateDelta(&processItem->CpuKernelDelta, process->KernelTime.QuadPart);
2144  PhUpdateDelta(&processItem->CpuUserDelta, process->UserTime.QuadPart);
2145  PhUpdateDelta(&processItem->IoReadDelta, process->ReadTransferCount.QuadPart);
2146  PhUpdateDelta(&processItem->IoWriteDelta, process->WriteTransferCount.QuadPart);
2147  PhUpdateDelta(&processItem->IoOtherDelta, process->OtherTransferCount.QuadPart);
2148  PhUpdateDelta(&processItem->IoReadCountDelta, process->ReadOperationCount.QuadPart);
2149  PhUpdateDelta(&processItem->IoWriteCountDelta, process->WriteOperationCount.QuadPart);
2150  PhUpdateDelta(&processItem->IoOtherCountDelta, process->OtherOperationCount.QuadPart);
2151  PhUpdateDelta(&processItem->ContextSwitchesDelta, contextSwitches);
2152  PhUpdateDelta(&processItem->PageFaultsDelta, process->PageFaultCount);
2153  PhUpdateDelta(&processItem->CycleTimeDelta, process->CycleTime);
2154  PhUpdateDelta(&processItem->PrivateBytesDelta, process->PagefileUsage);
2155 
2156  processItem->IsSuspended = isSuspended;
2157  processItem->IsPartiallySuspended = isPartiallySuspended;
2158 
2159  // If this is the first run of the provider, queue the
2160  // process query tasks. Otherwise, perform stage 1
2161  // processing now and queue stage 2 processing.
2162  if (runCount > 0)
2163  {
2165 
2166  memset(&data, 0, sizeof(PH_PROCESS_QUERY_S1_DATA));
2167  data.Header.Stage = 1;
2168  data.Header.ProcessItem = processItem;
2169  PhpProcessQueryStage1(&data);
2170  PhpFillProcessItemStage1(&data);
2171  PhSetEvent(&processItem->Stage1Event);
2172  }
2173  else
2174  {
2175  PhpQueueProcessQueryStage1(processItem);
2176  }
2177 
2178  // Add pending service items to the process item.
2179  PhUpdateProcessItemServices(processItem);
2180 
2181  // Add the process item to the hashtable.
2182  PhAcquireQueuedLockExclusive(&PhProcessHashSetLock);
2183  PhpAddProcessItem(processItem);
2184  PhReleaseQueuedLockExclusive(&PhProcessHashSetLock);
2185 
2186  // Raise the process added event.
2187  PhInvokeCallback(&PhProcessAddedEvent, processItem);
2188  processItem->AddedEventSent = TRUE;
2189 
2190  // (Ref: for the process item being in the hashtable.)
2191  // Instead of referencing then dereferencing we simply don't do anything.
2192  // Dereferenced in PhpRemoveProcessItem.
2193  }
2194  else
2195  {
2196  BOOLEAN modified = FALSE;
2197  BOOLEAN isSuspended;
2198  BOOLEAN isPartiallySuspended;
2199  ULONG contextSwitches;
2200  FLOAT newCpuUsage;
2201  FLOAT kernelCpuUsage;
2202  FLOAT userCpuUsage;
2203 
2204  PhpGetProcessThreadInformation(process, &isSuspended, &isPartiallySuspended, &contextSwitches);
2205  PhpUpdateDynamicInfoProcessItem(processItem, process);
2206 
2207  // Update the deltas.
2208  PhUpdateDelta(&processItem->CpuKernelDelta, process->KernelTime.QuadPart);
2209  PhUpdateDelta(&processItem->CpuUserDelta, process->UserTime.QuadPart);
2210  PhUpdateDelta(&processItem->IoReadDelta, process->ReadTransferCount.QuadPart);
2211  PhUpdateDelta(&processItem->IoWriteDelta, process->WriteTransferCount.QuadPart);
2212  PhUpdateDelta(&processItem->IoOtherDelta, process->OtherTransferCount.QuadPart);
2213  PhUpdateDelta(&processItem->IoReadCountDelta, process->ReadOperationCount.QuadPart);
2214  PhUpdateDelta(&processItem->IoWriteCountDelta, process->WriteOperationCount.QuadPart);
2215  PhUpdateDelta(&processItem->IoOtherCountDelta, process->OtherOperationCount.QuadPart);
2216  PhUpdateDelta(&processItem->ContextSwitchesDelta, contextSwitches);
2217  PhUpdateDelta(&processItem->PageFaultsDelta, process->PageFaultCount);
2218  PhUpdateDelta(&processItem->CycleTimeDelta, process->CycleTime);
2219  PhUpdateDelta(&processItem->PrivateBytesDelta, process->PagefileUsage);
2220 
2221  processItem->SequenceNumber++;
2222  PhAddItemCircularBuffer_ULONG64(&processItem->IoReadHistory, processItem->IoReadDelta.Delta);
2223  PhAddItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, processItem->IoWriteDelta.Delta);
2224  PhAddItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, processItem->IoOtherDelta.Delta);
2225 
2226  PhAddItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, processItem->VmCounters.PagefileUsage);
2227  //PhAddItemCircularBuffer_SIZE_T(&processItem->WorkingSetHistory, processItem->VmCounters.WorkingSetSize);
2228 
2229  if (InterlockedExchange(&processItem->JustProcessed, 0) != 0)
2230  modified = TRUE;
2231 
2232  if (isCycleCpuUsageEnabled)
2233  {
2234  FLOAT totalDelta;
2235 
2236  newCpuUsage = (FLOAT)processItem->CycleTimeDelta.Delta / sysTotalCycleTime;
2237 
2238  // Calculate the kernel/user CPU usage based on the kernel/user time. If the kernel and
2239  // user deltas are both zero, we'll just have to use an estimate. Currently, we split
2240  // the CPU usage evenly across the kernel and user components, except when the total
2241  // user time is zero, in which case we assign it all to the kernel component.
2242 
2243  totalDelta = (FLOAT)(processItem->CpuKernelDelta.Delta + processItem->CpuUserDelta.Delta);
2244 
2245  if (totalDelta != 0)
2246  {
2247  kernelCpuUsage = newCpuUsage * ((FLOAT)processItem->CpuKernelDelta.Delta / totalDelta);
2248  userCpuUsage = newCpuUsage * ((FLOAT)processItem->CpuUserDelta.Delta / totalDelta);
2249  }
2250  else
2251  {
2252  if (processItem->UserTime.QuadPart != 0)
2253  {
2254  kernelCpuUsage = newCpuUsage / 2;
2255  userCpuUsage = newCpuUsage / 2;
2256  }
2257  else
2258  {
2259  kernelCpuUsage = newCpuUsage;
2260  userCpuUsage = 0;
2261  }
2262  }
2263  }
2264  else
2265  {
2266  kernelCpuUsage = (FLOAT)processItem->CpuKernelDelta.Delta / sysTotalTime;
2267  userCpuUsage = (FLOAT)processItem->CpuUserDelta.Delta / sysTotalTime;
2268  newCpuUsage = kernelCpuUsage + userCpuUsage;
2269  }
2270 
2271  processItem->CpuUsage = newCpuUsage;
2272  processItem->CpuKernelUsage = kernelCpuUsage;
2273  processItem->CpuUserUsage = userCpuUsage;
2274 
2275  PhAddItemCircularBuffer_FLOAT(&processItem->CpuKernelHistory, kernelCpuUsage);
2276  PhAddItemCircularBuffer_FLOAT(&processItem->CpuUserHistory, userCpuUsage);
2277 
2278  // Max. values
2279 
2280  if (processItem->ProcessId != NULL)
2281  {
2282  if (maxCpuValue < newCpuUsage)
2283  {
2284  maxCpuValue = newCpuUsage;
2285  maxCpuProcessItem = processItem;
2286  }
2287 
2288  // I/O for Other is not included because it is too generic.
2289  if (maxIoValue < processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta)
2290  {
2291  maxIoValue = processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta;
2292  maxIoProcessItem = processItem;
2293  }
2294  }
2295 
2296  // Debugged
2297  if (processItem->QueryHandle)
2298  {
2299  BOOLEAN isBeingDebugged;
2300 
2302  processItem->QueryHandle,
2303  &isBeingDebugged
2304  )) && processItem->IsBeingDebugged != isBeingDebugged)
2305  {
2306  processItem->IsBeingDebugged = isBeingDebugged;
2307  modified = TRUE;
2308  }
2309  }
2310 
2311  // Suspended
2312  if (processItem->IsSuspended != isSuspended)
2313  {
2314  processItem->IsSuspended = isSuspended;
2315  modified = TRUE;
2316  }
2317 
2318  processItem->IsPartiallySuspended = isPartiallySuspended;
2319 
2320  // .NET
2321  if (processItem->UpdateIsDotNet)
2322  {
2323  BOOLEAN isDotNet;
2324 
2325  if (NT_SUCCESS(PhGetProcessIsDotNet(processItem->ProcessId, &isDotNet)))
2326  {
2327  processItem->IsDotNet = isDotNet;
2328  modified = TRUE;
2329  }
2330 
2331  processItem->UpdateIsDotNet = FALSE;
2332  }
2333 
2334  // Immersive
2335  if (processItem->QueryHandle && IsImmersiveProcess_I)
2336  {
2337  BOOLEAN isImmersive;
2338 
2339  isImmersive = !!IsImmersiveProcess_I(processItem->QueryHandle);
2340 
2341  if (processItem->IsImmersive != isImmersive)
2342  {
2343  processItem->IsImmersive = isImmersive;
2344  modified = TRUE;
2345  }
2346  }
2347 
2348  if (modified)
2349  {
2351  }
2352 
2353  // No reference added by PhpLookupProcessItem.
2354  }
2355 
2356  // Trick ourselves into thinking that the fake processes
2357  // are on the list.
2358  if (process == &PhInterruptsProcessInformation)
2359  {
2360  process = NULL;
2361  }
2362  else if (process == &PhDpcsProcessInformation)
2363  {
2364  process = &PhInterruptsProcessInformation;
2365  }
2366  else
2367  {
2368  process = PH_NEXT_PROCESS(process);
2369 
2370  if (process == NULL)
2371  {
2372  if (isCycleCpuUsageEnabled)
2373  process = &PhInterruptsProcessInformation;
2374  else
2375  process = &PhDpcsProcessInformation;
2376  }
2377  }
2378  }
2379 
2382 
2383  PhProcessInformation = processes;
2384 
2385  if (PhpTsProcesses)
2386  {
2387  WinStationFreeGAPMemory(0, PhpTsProcesses, PhpTsNumberOfProcesses);
2388  PhpTsProcesses = NULL;
2389  }
2390 
2391  // History cannot be updated on the first run because the deltas are invalid.
2392  // For example, the I/O "deltas" will be huge because they are currently the
2393  // raw accumulated values.
2394  if (runCount != 0)
2395  {
2396  if (isCycleCpuUsageEnabled)
2397  PhpUpdateCpuCycleUsageInformation(sysTotalCycleTime, sysIdleCycleTime);
2398 
2400 
2401  // Note that we need to add a reference to the records of these processes, to
2402  // make it possible for others to get the name of a max. CPU or I/O process.
2403 
2404  if (maxCpuProcessItem)
2405  {
2406  PhAddItemCircularBuffer_ULONG(&PhMaxCpuHistory, (ULONG)maxCpuProcessItem->ProcessId);
2407 #ifdef PH_RECORD_MAX_USAGE
2408  PhAddItemCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, maxCpuProcessItem->CpuUsage);
2409 #endif
2410 
2411  if (!(maxCpuProcessItem->Record->Flags & PH_PROCESS_RECORD_STAT_REF))
2412  {
2413  PhReferenceProcessRecord(maxCpuProcessItem->Record);
2414  maxCpuProcessItem->Record->Flags |= PH_PROCESS_RECORD_STAT_REF;
2415  }
2416  }
2417  else
2418  {
2419  PhAddItemCircularBuffer_ULONG(&PhMaxCpuHistory, (ULONG)NULL);
2420 #ifdef PH_RECORD_MAX_USAGE
2421  PhAddItemCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, 0);
2422 #endif
2423  }
2424 
2425  if (maxIoProcessItem)
2426  {
2427  PhAddItemCircularBuffer_ULONG(&PhMaxIoHistory, (ULONG)maxIoProcessItem->ProcessId);
2428 #ifdef PH_RECORD_MAX_USAGE
2429  PhAddItemCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory,
2430  maxIoProcessItem->IoReadDelta.Delta + maxIoProcessItem->IoOtherDelta.Delta);
2431  PhAddItemCircularBuffer_ULONG64(&PhMaxIoWriteHistory, maxIoProcessItem->IoWriteDelta.Delta);
2432 #endif
2433 
2434  if (!(maxIoProcessItem->Record->Flags & PH_PROCESS_RECORD_STAT_REF))
2435  {
2436  PhReferenceProcessRecord(maxIoProcessItem->Record);
2437  maxIoProcessItem->Record->Flags |= PH_PROCESS_RECORD_STAT_REF;
2438  }
2439  }
2440  else
2441  {
2442  PhAddItemCircularBuffer_ULONG(&PhMaxIoHistory, (ULONG)NULL);
2443 #ifdef PH_RECORD_MAX_USAGE
2444  PhAddItemCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, 0);
2445  PhAddItemCircularBuffer_ULONG64(&PhMaxIoWriteHistory, 0);
2446 #endif
2447  }
2448  }
2449 
2451  runCount++;
2452 }
2453 
2455  _In_ PPH_PROCESS_ITEM ProcessItem
2456  )
2457 {
2458  PPH_PROCESS_RECORD processRecord;
2459 
2460  processRecord = PhAllocate(sizeof(PH_PROCESS_RECORD));
2461  memset(processRecord, 0, sizeof(PH_PROCESS_RECORD));
2462 
2463  InitializeListHead(&processRecord->ListEntry);
2464  processRecord->RefCount = 1;
2465 
2466  processRecord->ProcessId = ProcessItem->ProcessId;
2467  processRecord->ParentProcessId = ProcessItem->ParentProcessId;
2468  processRecord->SessionId = ProcessItem->SessionId;
2469  processRecord->CreateTime = ProcessItem->CreateTime;
2470 
2471  PhSetReference(&processRecord->ProcessName, ProcessItem->ProcessName);
2472  PhSetReference(&processRecord->FileName, ProcessItem->FileName);
2473  PhSetReference(&processRecord->CommandLine, ProcessItem->CommandLine);
2474  //PhSetReference(&processRecord->UserName, ProcessItem->UserName);
2475 
2476  return processRecord;
2477 }
2478 
2480  _In_ PLARGE_INTEGER Time,
2481  _Out_opt_ PULONG Index,
2482  _Out_opt_ PULONG InsertIndex
2483  )
2484 {
2485  PPH_PROCESS_RECORD processRecord;
2486  LONG low;
2487  LONG high;
2488  LONG i;
2489  BOOLEAN found = FALSE;
2490  INT comparison;
2491 
2492  if (PhProcessRecordList->Count == 0)
2493  {
2494  if (Index)
2495  *Index = 0;
2496  if (InsertIndex)
2497  *InsertIndex = 0;
2498 
2499  return NULL;
2500  }
2501 
2502  low = 0;
2503  high = PhProcessRecordList->Count - 1;
2504 
2505  // Do a binary search.
2506  do
2507  {
2508  i = (low + high) / 2;
2509  processRecord = (PPH_PROCESS_RECORD)PhProcessRecordList->Items[i];
2510 
2511  comparison = uint64cmp(Time->QuadPart, processRecord->CreateTime.QuadPart);
2512 
2513  if (comparison == 0)
2514  {
2515  found = TRUE;
2516  break;
2517  }
2518  else if (comparison < 0)
2519  {
2520  high = i - 1;
2521  }
2522  else
2523  {
2524  low = i + 1;
2525  }
2526  } while (low <= high);
2527 
2528  if (Index)
2529  *Index = i;
2530 
2531  if (found)
2532  {
2533  return processRecord;
2534  }
2535  else
2536  {
2537  if (InsertIndex)
2538  *InsertIndex = i + (comparison > 0);
2539 
2540  return NULL;
2541  }
2542 }
2543 
2545  _Inout_ PPH_PROCESS_RECORD ProcessRecord
2546  )
2547 {
2548  PPH_PROCESS_RECORD processRecord;
2549  ULONG insertIndex;
2550 
2551  PhAcquireQueuedLockExclusive(&PhProcessRecordListLock);
2552 
2553  processRecord = PhpSearchProcessRecordList(&ProcessRecord->CreateTime, NULL, &insertIndex);
2554 
2555  if (!processRecord)
2556  {
2557  // Insert the process record, keeping the list sorted.
2558  PhInsertItemList(PhProcessRecordList, insertIndex, ProcessRecord);
2559  }
2560  else
2561  {
2562  // Link the process record with the existing one that we found.
2563  InsertTailList(&processRecord->ListEntry, &ProcessRecord->ListEntry);
2564  }
2565 
2566  PhReleaseQueuedLockExclusive(&PhProcessRecordListLock);
2567 }
2568 
2570  _Inout_ PPH_PROCESS_RECORD ProcessRecord
2571  )
2572 {
2573  ULONG i;
2574  PPH_PROCESS_RECORD headProcessRecord;
2575 
2576  PhAcquireQueuedLockExclusive(&PhProcessRecordListLock);
2577 
2578  headProcessRecord = PhpSearchProcessRecordList(&ProcessRecord->CreateTime, &i, NULL);
2579  assert(headProcessRecord);
2580 
2581  // Unlink the record from the list.
2582  RemoveEntryList(&ProcessRecord->ListEntry);
2583 
2584  if (ProcessRecord == headProcessRecord)
2585  {
2586  // Remove the slot completely, or choose a new head record.
2587  if (IsListEmpty(&headProcessRecord->ListEntry))
2588  PhRemoveItemList(PhProcessRecordList, i);
2589  else
2590  PhProcessRecordList->Items[i] = CONTAINING_RECORD(headProcessRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry);
2591  }
2592 
2593  PhReleaseQueuedLockExclusive(&PhProcessRecordListLock);
2594 }
2595 
2597  _In_ PPH_PROCESS_RECORD ProcessRecord
2598  )
2599 {
2600  _InterlockedIncrement(&ProcessRecord->RefCount);
2601 }
2602 
2604  _In_ PPH_PROCESS_RECORD ProcessRecord
2605  )
2606 {
2607  return _InterlockedIncrementNoZero(&ProcessRecord->RefCount);
2608 }
2609 
2611  _In_ PPH_PROCESS_RECORD ProcessRecord
2612  )
2613 {
2614  if (!(ProcessRecord->Flags & PH_PROCESS_RECORD_STAT_REF))
2615  {
2616  PhReferenceProcessRecord(ProcessRecord);
2617  ProcessRecord->Flags |= PH_PROCESS_RECORD_STAT_REF;
2618  }
2619 }
2620 
2622  _In_ PPH_PROCESS_RECORD ProcessRecord
2623  )
2624 {
2625  if (_InterlockedDecrement(&ProcessRecord->RefCount) == 0)
2626  {
2627  PhpRemoveProcessRecord(ProcessRecord);
2628 
2629  PhDereferenceObject(ProcessRecord->ProcessName);
2630  if (ProcessRecord->FileName) PhDereferenceObject(ProcessRecord->FileName);
2631  if (ProcessRecord->CommandLine) PhDereferenceObject(ProcessRecord->CommandLine);
2632  /*if (ProcessRecord->UserName) PhDereferenceObject(ProcessRecord->UserName);*/
2633  PhFree(ProcessRecord);
2634  }
2635 }
2636 
2638  _In_ PPH_PROCESS_RECORD ProcessRecord,
2639  _In_ HANDLE ProcessId
2640  )
2641 {
2642  PPH_PROCESS_RECORD startProcessRecord;
2643 
2644  startProcessRecord = ProcessRecord;
2645 
2646  do
2647  {
2648  if (ProcessRecord->ProcessId == ProcessId)
2649  return ProcessRecord;
2650 
2651  ProcessRecord = CONTAINING_RECORD(ProcessRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry);
2652  } while (ProcessRecord != startProcessRecord);
2653 
2654  return NULL;
2655 }
2656 
2669  _In_opt_ HANDLE ProcessId,
2670  _In_ PLARGE_INTEGER Time
2671  )
2672 {
2673  PPH_PROCESS_RECORD processRecord;
2674  ULONG i;
2675  BOOLEAN found;
2676 
2677  if (PhProcessRecordList->Count == 0)
2678  return NULL;
2679 
2680  PhAcquireQueuedLockShared(&PhProcessRecordListLock);
2681 
2682  processRecord = PhpSearchProcessRecordList(Time, &i, NULL);
2683 
2684  if (!processRecord)
2685  {
2686  // This is expected. Now we search backwards to find the newest matching element
2687  // older than the given time.
2688 
2689  found = FALSE;
2690 
2691  while (TRUE)
2692  {
2693  processRecord = (PPH_PROCESS_RECORD)PhProcessRecordList->Items[i];
2694 
2695  if (processRecord->CreateTime.QuadPart < Time->QuadPart)
2696  {
2697  if (ProcessId)
2698  {
2699  processRecord = PhpFindProcessRecord(processRecord, ProcessId);
2700 
2701  if (processRecord)
2702  found = TRUE;
2703  }
2704  else
2705  {
2706  found = TRUE;
2707  }
2708 
2709  if (found)
2710  break;
2711  }
2712 
2713  if (i == 0)
2714  break;
2715 
2716  i--;
2717  }
2718  }
2719  else
2720  {
2721  if (ProcessId)
2722  {
2723  processRecord = PhpFindProcessRecord(processRecord, ProcessId);
2724 
2725  if (processRecord)
2726  found = TRUE;
2727  else
2728  found = FALSE;
2729  }
2730  else
2731  {
2732  found = TRUE;
2733  }
2734  }
2735 
2736  if (found)
2737  {
2738  // The record might have had its last reference just cleared but it hasn't
2739  // been removed from the list yet.
2740  if (!PhReferenceProcessRecordSafe(processRecord))
2741  found = FALSE;
2742  }
2743 
2744  PhReleaseQueuedLockShared(&PhProcessRecordListLock);
2745 
2746  if (found)
2747  return processRecord;
2748  else
2749  return NULL;
2750 }
2751 
2756  VOID
2757  )
2758 {
2759  PPH_PROCESS_RECORD processRecord;
2760  PPH_PROCESS_RECORD startProcessRecord;
2761  ULONG i;
2762  LARGE_INTEGER threshold;
2763  PPH_LIST derefList = NULL;
2764 
2765  if (PhProcessRecordList->Count == 0)
2766  return;
2767 
2768  // Get the oldest statistics time.
2769  PhGetStatisticsTime(NULL, PhTimeHistory.Count - 1, &threshold);
2770 
2771  PhAcquireQueuedLockShared(&PhProcessRecordListLock);
2772 
2773  for (i = 0; i < PhProcessRecordList->Count; i++)
2774  {
2775  processRecord = PhProcessRecordList->Items[i];
2776  startProcessRecord = processRecord;
2777 
2778  do
2779  {
2780  ULONG requiredFlags;
2781 
2783 
2784  if ((processRecord->Flags & requiredFlags) == requiredFlags)
2785  {
2786  // Check if the process exit time is before the oldest statistics time.
2787  // If so we can dereference the process record.
2788  if (processRecord->ExitTime.QuadPart < threshold.QuadPart)
2789  {
2790  // Clear the stat ref bit; this is to make sure we don't try to
2791  // dereference the record twice (e.g. if someone else currently holds
2792  // a reference to the record and it doesn't get removed immediately).
2793  processRecord->Flags &= ~PH_PROCESS_RECORD_STAT_REF;
2794 
2795  if (!derefList)
2796  derefList = PhCreateList(2);
2797 
2798  PhAddItemList(derefList, processRecord);
2799  }
2800  }
2801 
2802  processRecord = CONTAINING_RECORD(processRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry);
2803  } while (processRecord != startProcessRecord);
2804  }
2805 
2806  PhReleaseQueuedLockShared(&PhProcessRecordListLock);
2807 
2808  if (derefList)
2809  {
2810  for (i = 0; i < derefList->Count; i++)
2811  {
2812  PhDereferenceProcessRecord(derefList->Items[i]);
2813  }
2814 
2815  PhDereferenceObject(derefList);
2816  }
2817 }
2818 
2820  _In_ HANDLE ParentProcessId,
2821  _In_ HANDLE ProcessId,
2822  _In_ PLARGE_INTEGER CreateTime
2823  )
2824 {
2825  PPH_PROCESS_ITEM processItem;
2826 
2827  if (ParentProcessId == ProcessId) // for cases where the parent PID = PID (e.g. System Idle Process)
2828  return NULL;
2829 
2830  PhAcquireQueuedLockShared(&PhProcessHashSetLock);
2831 
2832  processItem = PhpLookupProcessItem(ParentProcessId);
2833 
2834  // We make sure that the process item we found is actually the parent
2835  // process - its start time must not be larger than the supplied
2836  // time.
2837  if (processItem && processItem->CreateTime.QuadPart <= CreateTime->QuadPart)
2838  PhReferenceObject(processItem);
2839  else
2840  processItem = NULL;
2841 
2842  PhReleaseQueuedLockShared(&PhProcessHashSetLock);
2843 
2844  return processItem;
2845 }
2846 
2848  _In_ PPH_PROCESS_RECORD Record
2849  )
2850 {
2851  PPH_PROCESS_ITEM processItem;
2852 
2853  PhAcquireQueuedLockShared(&PhProcessHashSetLock);
2854 
2855  processItem = PhpLookupProcessItem(Record->ProcessId);
2856 
2857  if (processItem && processItem->CreateTime.QuadPart == Record->CreateTime.QuadPart)
2858  PhReferenceObject(processItem);
2859  else
2860  processItem = NULL;
2861 
2862  PhReleaseQueuedLockShared(&PhProcessHashSetLock);
2863 
2864  return processItem;
2865 }