This sample demonstrates how to list all processes and their threads on Windows NT using the undocumented ZwQuerySystemInformation function. This function, which prototype is shown below, provides access to a wide variety of system information. http://www.alexfedotov.com/code/threads.zip
NTSTATUS ZwQuerySystemInformation( IN ULONG SystemInformationClass, // information class IN OUT PVOID SystemInformation, // information buffer IN ULONG SystemInformationLength, // size of information buffer OUT PULONG ReturnLength OPTIONAL // receives information length );
The first parameter, SystemInformationClass, specifies the type of information to retrieve. To obtain information about processes and threads, this parameter should be set to 5.
The second parameter, SystemInformation, should point to a buffer into which the system will store the requested information. For processes and threads, the information is returned as an array of SYSTEM_PROCESS_INFORMATION structures, one structure for each process in the system. The SYSTEM_PROCESS_INFORMATION structure contains most of the information that is displayed by the Windows NT Task Manager and also includes an array of SYSTEM_THREAD_INFORMATION structures that describe threads of the process.
typedef struct _SYSTEM_THREAD_INFORMATION { LARGE_INTEGER KernelTime; // time spent in kernel mode LARGE_INTEGER UserTime; // time spent in user mode LARGE_INTEGER CreateTime; // thread creation time ULONG WaitTime; // wait time PVOID StartAddress; // start address CLIENT_ID ClientId; // thread and process IDs KPRIORITY Priority; // dynamic priority KPRIORITY BasePriority; // base priority ULONG ContextSwitchCount; // number of context switches LONG State; // current state LONG WaitReason; // wait reason } SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION; typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryDelta; // offset to the next entry ULONG ThreadCount; // number of threads ULONG Reserved1[6]; // reserved LARGE_INTEGER CreateTime; // process creation time LARGE_INTEGER UserTime; // time spent in user mode LARGE_INTEGER KernelTime; // time spent in kernel mode UNICODE_STRING ProcessName; // process name KPRIORITY BasePriority; // base process priority ULONG ProcessId; // process identifier ULONG InheritedFromProcessId; // parent process identifier ULONG HandleCount; // number of handles ULONG Reserved2[2]; // reserved VM_COUNTERS VmCounters; // virtual memory counters #if _WIN32_WINNT >= 0x500 IO_COUNTERS IoCounters; // i/o counters #endif SYSTEM_THREAD_INFORMATION Threads[1]; // threads } SYSTEM_PROCESS_INFORMATION, * SYSTEM_PROCESS_INFORMATION;
Note that the size of the SYSTEM_PROCESS_INFORMATION structure differs between Windows NT 4.0 and Windows 2000. This is a perfect example why you should not use undocumented functions.
Below is the source code of the sample application. The application detemines the operating system version to handle correctly differences in the process information structure. Then the application requests processes and threads information and displays it on the screen.
int _tmain( int argc, _TCHAR * argv[] ) { // determine operating system version OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); ULONG cbBuffer = 0x8000; LPVOID pBuffer = NULL; NTSTATUS Status; // it is difficult to detemine a priory which size of the // buffer will be enough to retrieve all information, so we // start with 32K buffer and increase its size until we get // the information successfully do { pBuffer = malloc(cbBuffer); if (pBuffer == NULL) { _tprintf(_T("Not enough memory/n")); return 1; } Status = ZwQuerySystemInformation( SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL); if (Status == STATUS_INFO_LENGTH_MISMATCH) { free(pBuffer); cbBuffer *= 2; } else if (!NT_SUCCESS(Status)) { _tprintf(_T("ZwQuerySystemInformation failed with") _T("status 0x%08X/n"), Status); free(pBuffer); return 1; } } while (Status == STATUS_INFO_LENGTH_MISMATCH); PSYSTEM_PROCESS_INFORMATION pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer; for (;;) { PCWSTR pszProcessName = pInfo->ProcessName.Buffer; if (pszProcessName == NULL) pszProcessName = L"Idle"; _tprintf(_T("ProcessID: %d (%ls)/n"), pInfo->ProcessId, pszProcessName); ULONG ThreadCount = pInfo->ThreadCount; PSYSTEM_THREAD_INFORMATION pThreads; if (osvi.dwMajorVersion < 5) pThreads = ((PSYSTEM_PROCESS_INFORMATION_NT4)pInfo)->Threads; else pThreads = pInfo->Threads; for (ULONG i = 0; i < ThreadCount; i++) { DWORD dwThreadId = pThreads[i].ClientId.UniqueThread; _tprintf(_T("/tThreadID: %d/n"), dwThreadId); TestOpenThread(dwThreadId); } if (pInfo->NextEntryDelta == 0) break; // find the address of the next process structure pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta); } free(pBuffer); return 0; }
Note: The method described is not the only way how the list of processes and threads can be obtained. Other methods include performance data interface, PSAPI (for the list of processes), Toolhelp32 API (available in Windows 2000 and later) and Windows Management Instrumentation (WMI). It is strongly recommented to use one of documented methods to obtain this information rather than call ZwQuerySystemInformation.
Among other enhancements, a new Win32 API function named OpenThread was added in Windows 2000. This function returns a handle of an existing thread given the thread identifier. This sample shows how OpenThread can be implemented on earlier versions of Windows NT using the undocumented ZwOpenThread function.
Below is the source code of the OpenThread replacement. I won't be surprised if the actual OpenThread code doesn't differ much from what you see here.
HANDLE OpenThread( DWORD dwDesiredAccess, // access right BOOL bInheritHandle, // handle inheritance option DWORD dwThreadId // thread identifier ) { OBJECT_ATTRIBUTES ObjectAttributes; CLIENT_ID ClientId; HANDLE hThread; NTSTATUS Status; memset(&ObjectAttributes, 0, sizeof(ObjectAttributes)); if (bInheritHandle) ObjectAttributes.Attributes = OBJ_INHERIT; ClientId.UniqueProcess = NULL; ClientId.UniqueThread = (HANDLE)dwThreadId; Status = ZwOpenThread(&hThread, dwDesiredAccess, &ObjectAttributes, &ClientId); if (!NT_SUCCESS(Status)) { SetLastError(LsaNtStatusToWinError(Status)); return NULL; } return hThread; }