|
|
/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
pmon.c
Abstract:
This module contains the NT/Win32 Process Monitor
Author:
Lou Perazzoli (loup) 1-Jan-1993
Revision History:
--*/
#include "perfmtrp.h"
#include <search.h>
#include <malloc.h>
#include <limits.h>
#include <stdlib.h>
#define BUFFER_SIZE 64*1024
#define MAX_BUFFER_SIZE 10*1024*1024
ULONG CurrentBufferSize; PUCHAR PreviousBuffer; PUCHAR CurrentBuffer; PUCHAR TempBuffer;
#define CPU_USAGE 0
#define QUOTAS 1
USHORT *NoNameFound = L"Unknown"; USHORT *IdleProcess = L"Idle Process";
UCHAR *StateTable[] = { "Initialized", "Ready", "Running", "Standby", "Terminated", "Wait:", "Transition", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown" };
BOOLEAN Interactive; ULONG NumberOfInputRecords; //INPUT_RECORD InputRecord;
HANDLE InputHandle; HANDLE OriginalOutputHandle; HANDLE OutputHandle; DWORD OriginalInputMode; WORD NormalAttribute; WORD HighlightAttribute; ULONG NumberOfCols; ULONG NumberOfRows; ULONG NumberOfDetailLines; ULONG FirstDetailLine; CONSOLE_SCREEN_BUFFER_INFO OriginalConsoleInfo;
UCHAR *WaitTable[] = { "Executive", "FreePage", "PageIn", "PoolAllocation", "DelayExecution", "Suspended", "UserRequest", "Executive", "FreePage", "PageIn", "PoolAllocation", "DelayExecution", "Suspended", "UserRequest", "EventPairHigh", "EventPairLow", "LpcReceive", "LpcReply", "Spare1", "Spare2", "Spare3", "Spare4", "Spare5", "Spare6", "Spare7", "Spare8", "Spare9", "Unknown", "Unknown", "Unknown", "Unknown" };
UCHAR *Empty = " ";
PSYSTEM_PROCESS_INFORMATION FindMatchedProcess ( IN PSYSTEM_PROCESS_INFORMATION ProcessToMatch, IN PUCHAR SystemInfoBuffer, IN PULONG Hint );
PSYSTEM_THREAD_INFORMATION FindMatchedThread ( IN PSYSTEM_THREAD_INFORMATION ThreadToMatch, IN PSYSTEM_PROCESS_INFORMATION MatchedProcess );
typedef struct _TOPCPU { LARGE_INTEGER TotalTime; PSYSTEM_PROCESS_INFORMATION ProcessInfo; PSYSTEM_PROCESS_INFORMATION MatchedProcess; ULONG Value; LONG PageFaultDiff; SIZE_T WorkingSetDiff; } TOPCPU, *PTOPCPU;
// TOPCPU TopCpu[1000];
// Required for Terminal Services
PTOPCPU TopCpu; ULONG TopCpuSize; #define TOPCPU_BUFFER_SIZE (((300*sizeof(TOPCPU))/4096+1)*4096)
#define TOPCPU_MAX_BUFFER_SIZE (50*TOPCPU_BUFFER_SIZE)
BOOL WriteConsoleLine( HANDLE OutputHandle, WORD LineNumber, LPSTR Text, BOOL Highlight ) { COORD WriteCoord; DWORD NumberWritten; DWORD TextLength;
WriteCoord.X = 0; WriteCoord.Y = LineNumber; if (!FillConsoleOutputCharacter( OutputHandle, ' ', NumberOfCols, WriteCoord, &NumberWritten ) ) { return FALSE; }
if (Text == NULL || (TextLength = strlen( Text )) == 0) { return TRUE; } else { return WriteConsoleOutputCharacter( OutputHandle, Text, TextLength, WriteCoord, &NumberWritten ); } }
NTSTATUS GetProcessInfo ( IN PUCHAR p )
{ NTSTATUS Status;
retry01:
Status = NtQuerySystemInformation( SystemProcessInformation, p, CurrentBufferSize, NULL );
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
//
// Increase buffer size.
//
CurrentBufferSize += 8192;
TempBuffer = VirtualAlloc (CurrentBuffer, CurrentBufferSize, MEM_COMMIT, PAGE_READWRITE); if (TempBuffer == NULL) { printf("Memory commit failed\n"); ExitProcess(0); } TempBuffer = VirtualAlloc (PreviousBuffer, CurrentBufferSize, MEM_COMMIT, PAGE_READWRITE); if (TempBuffer == NULL) { printf("Memory commit failed\n"); ExitProcess(0); } goto retry01; } return Status; }
int __cdecl main( argc, argv ) int argc; char *argv[]; {
NTSTATUS Status; int i; ULONG DelayTimeMsec; ULONG DelayTimeTicks; ULONG LastCount; COORD cp; BOOLEAN Active; PSYSTEM_THREAD_INFORMATION Thread; SYSTEM_PERFORMANCE_INFORMATION PerfInfo; SYSTEM_FILECACHE_INFORMATION FileCache; SYSTEM_FILECACHE_INFORMATION PrevFileCache;
CHAR OutputBuffer[ 512 ]; UCHAR LastKey; LONG ScrollDelta; WORD DisplayLine, LastDetailRow; BOOLEAN DoQuit = FALSE;
ULONG SkipLine; ULONG Hint; ULONG Offset1; SIZE_T SumCommit; int num; int lastnum; PSYSTEM_PROCESS_INFORMATION CurProcessInfo; PSYSTEM_PROCESS_INFORMATION MatchedProcess; LARGE_INTEGER LARGE_ZERO={0,0}; LARGE_INTEGER Ktime; LARGE_INTEGER Utime; LARGE_INTEGER TotalTime; TIME_FIELDS TimeOut; PTOPCPU PTopCpu; SYSTEM_BASIC_INFORMATION BasicInfo; ULONG DisplayType = CPU_USAGE; INPUT_RECORD InputRecord; DWORD NumRead; ULONG Cpu; ULONG NoScreenChanges = FALSE;
OutputBuffer[sizeof(OutputBuffer) - 1] = 0; if ( GetPriorityClass(GetCurrentProcess()) == NORMAL_PRIORITY_CLASS) { if (SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS) == 0) { //
// Not much we can do
//
printf("Cannot elevate process priority\n"); return 0;
} }
InputHandle = GetStdHandle( STD_INPUT_HANDLE ); OriginalOutputHandle = GetStdHandle( STD_OUTPUT_HANDLE ); Interactive = TRUE; if (Interactive) { if (InputHandle == NULL || OriginalOutputHandle == NULL || !GetConsoleMode( InputHandle, &OriginalInputMode ) ) { Interactive = FALSE; } else { OutputHandle = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); if (OutputHandle == NULL || !GetConsoleScreenBufferInfo( OriginalOutputHandle, &OriginalConsoleInfo ) || !SetConsoleScreenBufferSize( OutputHandle, OriginalConsoleInfo.dwSize ) || !SetConsoleActiveScreenBuffer( OutputHandle ) || !SetConsoleMode( InputHandle, 0 ) ) { if (OutputHandle != NULL) { CloseHandle( OutputHandle ); OutputHandle = NULL; }
Interactive = FALSE; } else { NormalAttribute = 0x1F; HighlightAttribute = 0x71; NumberOfCols = OriginalConsoleInfo.dwSize.X; NumberOfRows = OriginalConsoleInfo.dwSize.Y; NumberOfDetailLines = NumberOfRows - 7; } } }
Status = NtQuerySystemInformation( SystemBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL );
Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo, sizeof(PerfInfo), NULL );
DelayTimeMsec = 10; DelayTimeTicks = DelayTimeMsec * 10000;
PreviousBuffer = VirtualAlloc (NULL, MAX_BUFFER_SIZE, MEM_RESERVE, PAGE_READWRITE); if (PreviousBuffer == NULL) { printf("Memory allocation failed\n"); return 0; }
TempBuffer = VirtualAlloc (PreviousBuffer, BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE);
if (TempBuffer == NULL) { printf("Memory commit failed\n"); return 0; }
CurrentBuffer = VirtualAlloc (NULL, MAX_BUFFER_SIZE, MEM_RESERVE, PAGE_READWRITE); if (CurrentBuffer == NULL) { printf("Memory allocation failed\n"); return 0; }
TempBuffer = VirtualAlloc (CurrentBuffer, BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); if (TempBuffer == NULL) { printf("Memory commit failed\n"); return 0; }
// TS
TopCpu = VirtualAlloc (NULL, TOPCPU_MAX_BUFFER_SIZE, MEM_RESERVE, PAGE_READWRITE); if(TopCpu == NULL) { printf("Memory allocation failed\n"); return 0; } TempBuffer = VirtualAlloc( TopCpu, TOPCPU_BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); if( TempBuffer == NULL ) { printf("Memory commit failed\n"); return 0; } num = 0;
TopCpuSize = TOPCPU_BUFFER_SIZE; CurrentBufferSize = BUFFER_SIZE;
TempBuffer = NULL;
Status = GetProcessInfo (PreviousBuffer);
if( !NT_SUCCESS( Status ) ) { printf("Get process information failed %lx\n", Status); return (0); }
DelayTimeMsec = 5000; DelayTimeTicks = DelayTimeMsec * 10000;
Status = GetProcessInfo (CurrentBuffer);
if( !NT_SUCCESS( Status ) ) { printf("Get process information failed %lx\n", Status); return (0); }
Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo, sizeof(PerfInfo), NULL ); LastCount = PerfInfo.PageFaultCount;
if ( !NT_SUCCESS(Status) ) { printf("Query perf Failed %lx\n",Status); return 0; }
Status = NtQuerySystemInformation( SystemFileCacheInformation, &FileCache, sizeof(FileCache), NULL ); PrevFileCache = FileCache;
if ( !NT_SUCCESS(Status) ) { printf("Query file cache Failed %lx\n",Status); return 0; }
Active = TRUE;
while(TRUE) { Status = GetProcessInfo (CurrentBuffer);
if( !NT_SUCCESS( Status ) ) { printf("Get process information failed %lx\n", Status); return (0); }
Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo, sizeof(PerfInfo), NULL );
if ( !NT_SUCCESS(Status) ) { printf("Query perf Failed %lx\n",Status); return 0; } Status = NtQuerySystemInformation( SystemFileCacheInformation, &FileCache, sizeof(FileCache), NULL );
if ( !NT_SUCCESS(Status) ) { printf("Query file cache Failed %lx\n",Status); return 0; }
//
// Calculate top CPU users and display information.
//
//
// Cross check previous process/thread info against current
// process/thread info.
//
Offset1 = 0; lastnum = num; num = 0; Hint = 0; TotalTime = LARGE_ZERO; SumCommit = 0; while (TRUE) { CurProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&CurrentBuffer[Offset1];
//
// Find the corresponding process in the previous array.
//
MatchedProcess = FindMatchedProcess (CurProcessInfo, PreviousBuffer, &Hint);
if( num >= (int)( TopCpuSize / sizeof( TOPCPU ) ) ) { TopCpuSize += 4096;
if( VirtualAlloc( TopCpu, TopCpuSize, MEM_COMMIT, PAGE_READWRITE ) == NULL ) { printf("Memory commit failed\n"); return 0; } }
if (MatchedProcess == NULL) { TopCpu[num].TotalTime = CurProcessInfo->KernelTime; TopCpu[num].TotalTime.QuadPart = TopCpu[num].TotalTime.QuadPart + CurProcessInfo->UserTime.QuadPart; TotalTime.QuadPart = TotalTime.QuadPart + TopCpu[num].TotalTime.QuadPart; TopCpu[num].ProcessInfo = CurProcessInfo; TopCpu[num].MatchedProcess = NULL; num += 1; } else { Ktime.QuadPart = CurProcessInfo->KernelTime.QuadPart - MatchedProcess->KernelTime.QuadPart; Utime.QuadPart = CurProcessInfo->UserTime.QuadPart - MatchedProcess->UserTime.QuadPart;
TopCpu[num].TotalTime.QuadPart = Ktime.QuadPart + Utime.QuadPart; TotalTime.QuadPart = TotalTime.QuadPart + TopCpu[num].TotalTime.QuadPart; TopCpu[num].ProcessInfo = CurProcessInfo; TopCpu[num].MatchedProcess = MatchedProcess; TopCpu[num].PageFaultDiff = CurProcessInfo->PageFaultCount - MatchedProcess->PageFaultCount; ; TopCpu[num].WorkingSetDiff = CurProcessInfo->WorkingSetSize - MatchedProcess->WorkingSetSize; num += 1; } SumCommit += CurProcessInfo->PrivatePageCount / 1024;
if (CurProcessInfo->NextEntryOffset == 0) {
DisplayLine = 0;
_snprintf (OutputBuffer, sizeof(OutputBuffer) - 1, " Memory:%8ldK Avail:%7ldK PageFlts:%6ld InRam Kernel:%5ldK P:%5ldK", BasicInfo.NumberOfPhysicalPages*(BasicInfo.PageSize/1024), PerfInfo.AvailablePages*(BasicInfo.PageSize/1024), PerfInfo.PageFaultCount - LastCount, (PerfInfo.ResidentSystemCodePage + PerfInfo.ResidentSystemDriverPage)*(BasicInfo.PageSize/1024), (PerfInfo.ResidentPagedPoolPage)*(BasicInfo.PageSize/1024) ); LastCount = PerfInfo.PageFaultCount; WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); _snprintf(OutputBuffer, sizeof(OutputBuffer) - 1, " Commit:%7ldK/%7ldK Limit:%7ldK Peak:%7ldK Pool N:%5ldK P:%5ldK", PerfInfo.CommittedPages*(BasicInfo.PageSize/1024), SumCommit, PerfInfo.CommitLimit*(BasicInfo.PageSize/1024), PerfInfo.PeakCommitment*(BasicInfo.PageSize/1024), PerfInfo.NonPagedPoolPages*(BasicInfo.PageSize/1024), PerfInfo.PagedPoolPages*(BasicInfo.PageSize/1024) ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE );
DisplayLine += 1;
if (NoScreenChanges) { DisplayLine += 2; } else { WriteConsoleLine( OutputHandle, DisplayLine++, " Mem Mem Page Flts Commit Usage Pri Hnd Thd Image ", FALSE );
WriteConsoleLine( OutputHandle, DisplayLine++, "CPU CpuTime Usage Diff Faults Diff Charge NonP Page Cnt Cnt Name ", FALSE ); }
DisplayLine += 1;
_snprintf(OutputBuffer, sizeof(OutputBuffer) - 1, " %6ld%5ld%9ld %4ld File Cache ", FileCache.CurrentSize/1024, ((LONG)FileCache.CurrentSize - (LONG)PrevFileCache.CurrentSize)/1024, FileCache.PageFaultCount, (LONG)FileCache.PageFaultCount - (LONG)PrevFileCache.PageFaultCount ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); PrevFileCache = FileCache;
LastDetailRow = (WORD)NumberOfRows; for (i = FirstDetailLine; i < num; i++) { if (DisplayLine >= LastDetailRow) { break; }
PTopCpu = &TopCpu[i]; Ktime.QuadPart = PTopCpu->ProcessInfo->KernelTime.QuadPart + PTopCpu->ProcessInfo->UserTime.QuadPart; RtlTimeToElapsedTimeFields ( &Ktime, &TimeOut); TimeOut.Hour += TimeOut.Day*24; if (PTopCpu->ProcessInfo->ImageName.Buffer == NULL) { if (PTopCpu->ProcessInfo->UniqueProcessId == (HANDLE)0) { PTopCpu->ProcessInfo->ImageName.Buffer = (PWSTR)IdleProcess; } else { PTopCpu->ProcessInfo->ImageName.Buffer = (PWSTR)NoNameFound; } } else { if (PTopCpu->ProcessInfo->ImageName.Length > 24) { PTopCpu->ProcessInfo->ImageName.Buffer += ((PTopCpu->ProcessInfo->ImageName.Length) - 24); } }
Cpu = PTopCpu->TotalTime.LowPart / ((TotalTime.LowPart / 100) ? (TotalTime.LowPart / 100) : 1); if ( Cpu == 100 ) { Cpu = 99; }
//
// See if nothing has changed.
//
SkipLine = FALSE; if ((PTopCpu->MatchedProcess != NULL) && (Cpu == 0) && (PTopCpu->WorkingSetDiff == 0) && (PTopCpu->PageFaultDiff == 0) && (PTopCpu->MatchedProcess->NumberOfThreads == PTopCpu->ProcessInfo->NumberOfThreads) && (PTopCpu->MatchedProcess->HandleCount == PTopCpu->ProcessInfo->HandleCount) && (PTopCpu->MatchedProcess->PrivatePageCount == PTopCpu->ProcessInfo->PrivatePageCount)) {
PTopCpu->ProcessInfo->PeakPagefileUsage = 0xffffffff; PTopCpu->ProcessInfo->PeakWorkingSetSize = DisplayLine;
if ((PTopCpu->MatchedProcess->PeakPagefileUsage == 0xffffffff) && (PTopCpu->MatchedProcess->PeakWorkingSetSize == DisplayLine) &&
(NoScreenChanges)) { SkipLine = TRUE; } }
if (SkipLine) {
//
// The line on the screen has not changed, just skip
// writing this one.
//
DisplayLine += 1; } else {
_snprintf(OutputBuffer, sizeof(OutputBuffer) - 1, "%2ld%4ld:%02ld:%02ld%7ld%5ld%9ld%5ld%7ld%5ld%5ld %2ld%5ld%3ld %ws", Cpu, TimeOut.Hour, TimeOut.Minute, TimeOut.Second, PTopCpu->ProcessInfo->WorkingSetSize / 1024, (ULONG)(PTopCpu->WorkingSetDiff / 1024), PTopCpu->ProcessInfo->PageFaultCount, PTopCpu->PageFaultDiff, PTopCpu->ProcessInfo->PrivatePageCount / 1024, PTopCpu->ProcessInfo->QuotaNonPagedPoolUsage / 1024, PTopCpu->ProcessInfo->QuotaPagedPoolUsage / 1024, PTopCpu->ProcessInfo->BasePriority, PTopCpu->ProcessInfo->HandleCount, PTopCpu->ProcessInfo->NumberOfThreads, PTopCpu->ProcessInfo->ImageName.Buffer );
WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE );
} Thread = (PSYSTEM_THREAD_INFORMATION)(TopCpu[i].ProcessInfo + 1); } while (lastnum > num) { WriteConsoleLine( OutputHandle, DisplayLine++, " ", FALSE); lastnum -= 1; } }
if (CurProcessInfo->NextEntryOffset == 0) { break; } Offset1 += CurProcessInfo->NextEntryOffset;
} //end while
TempBuffer = PreviousBuffer; PreviousBuffer = CurrentBuffer; CurrentBuffer = TempBuffer;
NoScreenChanges = TRUE; while (WaitForSingleObject( InputHandle, DelayTimeMsec ) == STATUS_WAIT_0) {
//
// Check for input record
//
if (ReadConsoleInput( InputHandle, &InputRecord, 1, &NumberOfInputRecords ) && InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown ) { LastKey = InputRecord.Event.KeyEvent.uChar.AsciiChar; if (LastKey < ' ') { ScrollDelta = 0; if (LastKey == 'C'-'A'+1) { DoQuit = TRUE; } else switch (InputRecord.Event.KeyEvent.wVirtualKeyCode) { case VK_ESCAPE: DoQuit = TRUE; break;
case VK_PRIOR: ScrollDelta = -(LONG)(InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines); break;
case VK_NEXT: ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines; break;
case VK_UP: ScrollDelta = -InputRecord.Event.KeyEvent.wRepeatCount; break;
case VK_DOWN: ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount; break;
case VK_HOME: FirstDetailLine = 0; break;
case VK_END:
if ((ULONG)num > NumberOfDetailLines) { FirstDetailLine = num - NumberOfDetailLines; NoScreenChanges = FALSE; } break; }
if (ScrollDelta != 0) { if (ScrollDelta < 0) { if (FirstDetailLine <= (ULONG)-ScrollDelta) { FirstDetailLine = 0; NoScreenChanges = FALSE; } else { FirstDetailLine += ScrollDelta; NoScreenChanges = FALSE; } } else { FirstDetailLine += ScrollDelta; NoScreenChanges = FALSE; if (FirstDetailLine >= (num - NumberOfDetailLines)) { FirstDetailLine = num - NumberOfDetailLines; } } } } else {
switch (toupper( LastKey )) { case 'C': case 'c': DisplayType = CPU_USAGE; break;
case 'P': case 'p': DisplayType = QUOTAS; break;
case 'q': case 'Q': DoQuit = TRUE; break;
default: break; } } break; } } if (DoQuit) { if (Interactive) { SetConsoleActiveScreenBuffer( OriginalOutputHandle ); SetConsoleMode( InputHandle, OriginalInputMode ); CloseHandle( OutputHandle ); } return 0; } } return 0; } PSYSTEM_PROCESS_INFORMATION FindMatchedProcess ( IN PSYSTEM_PROCESS_INFORMATION ProcessToMatch, IN PUCHAR SystemInfoBuffer, IN OUT PULONG Hint )
/*++
Routine Description:
This procedure finds the process which corresponds to the ProcessToMatch. It returns the address of the matching Process, or NULL if no matching process was found.
Arguments:
ProcessToMatch - Supplies a pointer to the target thread to match.
SystemInfoBuffer - Supples a pointer to the system information buffer in which to locate the process.
Hint - Supplies and returns a hint for optimizing the searches.
Return Value:
Address of the corresponding Process or NULL.
--*/
{ PSYSTEM_PROCESS_INFORMATION Process; ULONG Offset2;
Offset2 = *Hint;
while (TRUE) { Process = (PSYSTEM_PROCESS_INFORMATION)&SystemInfoBuffer[Offset2]; if ((Process->UniqueProcessId == ProcessToMatch->UniqueProcessId) && (Process->CreateTime.QuadPart == ProcessToMatch->CreateTime.QuadPart)) { *Hint = Offset2 + Process->NextEntryOffset; return(Process); } Offset2 += Process->NextEntryOffset; if (Offset2 == *Hint) { *Hint = 0; return(NULL); } if (Process->NextEntryOffset == 0) { if (*Hint == 0) { return(NULL); } Offset2 = 0; } } }
PSYSTEM_THREAD_INFORMATION FindMatchedThread ( IN PSYSTEM_THREAD_INFORMATION ThreadToMatch, IN PSYSTEM_PROCESS_INFORMATION MatchedProcess )
/*++
Routine Description:
This procedure finds thread which corresponds to the ThreadToMatch. It returns the address of the matching thread, or NULL if no matching thread was found.
Arguments:
ThreadToMatch - Supplies a pointer to the target thread to match.
MatchedProcess - Supples a pointer to the process which contains the target thread. The thread information must follow this process, i.e., this block was obtain from a NtQuerySystemInformation specifying PROCESS_INFORMATION.
Return Value:
Address of the corresponding thread from MatchedProcess or NULL.
--*/
{ PSYSTEM_THREAD_INFORMATION Thread; ULONG i;
Thread = (PSYSTEM_THREAD_INFORMATION)(MatchedProcess + 1); for (i = 0; i < MatchedProcess->NumberOfThreads; i++) { if ((Thread->ClientId.UniqueThread == ThreadToMatch->ClientId.UniqueThread) && (Thread->CreateTime.QuadPart == ThreadToMatch->CreateTime.QuadPart)) {
return(Thread); } Thread += 1; } return(NULL); }
|