- INT 2Eh Interrupt Gate
- Calling Into ntdll.dll
- Calling NtQuerySystemInformation()
- Enumerating Processes and Threads
- Putting the Pieces Together
- Sample Code Archive
- References
Enumerating Processes and Threads
In Listing 4, the thread and process structures are shown. Note the different process structure definitions for Windows NT 4.0 and 2000called SYSTEM_PROCESS_NT4 and SYSTEM_PROCESS_NT5, respectively. The SYSTEM_PROCESS_INFORMATION union at the end of Listing 4 is defined for convenience, allowing you to use a single data type for both operating systems. The Process item comprises all basic process structure members that are not version-specific, whereas Process_NT4 and Process_NT5 include all members that are available on Windows NT 4.0 and Windows 2000, respectively. If you are not interested in threads and Windows 2000 I/O counters, you can forget about the OS version and work with the common process structure only. Otherwise, you should use the Win32 API function GetVersionEx() to determine the major OS version of the system your application is currently running on.
Listing 4 Thread and Process Structures
typedef struct _SYSTEM_THREAD { /*000*/ FILETIME ftKernelTime; // 100 nsec units /*008*/ FILETIME ftUserTime; // 100 nsec units /*010*/ FILETIME ftCreateTime; // relative to 01-01-1601 /*018*/ DWORD dWaitTime; /*01C*/ PVOID pStartAddress; /*020*/ CLIENT_ID Cid; // process/thread ids /*028*/ DWORD dPriority; /*02C*/ DWORD dBasePriority; /*030*/ DWORD dContextSwitches; /*034*/ DWORD dThreadState; // 2=running, 5=waiting /*038*/ KWAIT_REASON WaitReason; /*03C*/ DWORD dReserved01; /*040*/ } SYSTEM_THREAD, *PSYSTEM_THREAD; #define SYSTEM_THREAD_ sizeof (SYSTEM_THREAD) // ----------------------------------------------------------------- typedef struct _SYSTEM_PROCESS // common members { /*000*/ DWORD dNext; // relative offset /*004*/ DWORD dThreadCount; /*008*/ DWORD dReserved01; /*00C*/ DWORD dReserved02; /*010*/ DWORD dReserved03; /*014*/ DWORD dReserved04; /*018*/ DWORD dReserved05; /*01C*/ DWORD dReserved06; /*020*/ FILETIME ftCreateTime; // relative to 01-01-1601 /*028*/ FILETIME ftUserTime; // 100 nsec units /*030*/ FILETIME ftKernelTime; // 100 nsec units /*038*/ UNICODE_STRING usName; /*040*/ KPRIORITY BasePriority; /*044*/ DWORD dUniqueProcessId; /*048*/ DWORD dInheritedFromUniqueProcessId; /*04C*/ DWORD dHandleCount; /*050*/ DWORD dReserved07; /*054*/ DWORD dReserved08; /*058*/ VM_COUNTERS VmCounters; // see ntddk.h /*084*/ DWORD dCommitCharge; // bytes /*088*/ } SYSTEM_PROCESS, *PSYSTEM_PROCESS; #define SYSTEM_PROCESS_ sizeof (SYSTEM_PROCESS) // ----------------------------------------------------------------- typedef struct _SYSTEM_PROCESS_NT4 // Windows NT 4.0 { /*000*/ SYSTEM_PROCESS Process; // common members /*088*/ SYSTEM_THREAD aThreads []; // thread array /*088*/ } SYSTEM_PROCESS_NT4, *PSYSTEM_PROCESS_NT4; #define SYSTEM_PROCESS_NT4_ sizeof (SYSTEM_PROCESS_NT4) // ----------------------------------------------------------------- typedef struct _SYSTEM_PROCESS_NT5 // Windows 2000 { /*000*/ SYSTEM_PROCESS Process; // common members /*088*/ IO_COUNTERS IoCounters; // see ntddk.h /*0B8*/ SYSTEM_THREAD aThreads []; // thread array /*0B8*/ } SYSTEM_PROCESS_NT5, *PSYSTEM_PROCESS_NT5; #define SYSTEM_PROCESS_NT5_ sizeof (SYSTEM_PROCESS_NT5) // ----------------------------------------------------------------- typedef union _SYSTEM_PROCESS_INFORMATION { /*000*/ SYSTEM_PROCESS Process; /*000*/ SYSTEM_PROCESS_NT4 Process_NT4; /*000*/ SYSTEM_PROCESS_NT5 Process_NT5; /*0B8*/ } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
In Listing 4, some data types appear that are not defined in the Platform SDK header filesfor example, CLIENT_ID, KPRIORITY, KWAIT_REASON, UNICODE_STRING, and VM_COUNTERS. These types are defined in the DDK header files, and must be added manually to your application code. But don't worryI have already done that for you! (Click here to download all the files and source code discussed in this article).
So, the last piece missing in this puzzle is the trial-and-error loop mentioned earlier, which will determine an output buffer size that approximately matches the required number of bytes. Listing 5 shows the details: The ProcessInformation() function defined there calls NtQuerySystemInformation() in a loop until it succeeds, or returns an error code other than STATUS_INFO_LENGTH_MISMATCH. On each try, the buffer size is doubled, starting with an initial size of 4096 bytes.
Listing 5 Retrieving the Process/Thread List
PSYSTEM_PROCESS_INFORMATION ProcessInformation (PDWORD pdData, PNTSTATUS pns) { DWORD dSize; DWORD dData = 0; NTSTATUS ns = STATUS_INVALID_PARAMETER; PSYSTEM_PROCESS_INFORMATION pspi = NULL; for (dSize = 0x1000; (pspi == NULL) && dSize; dSize <<= 1) { if ((pspi = LocalAlloc (LMEM_FIXED, dSize)) == NULL) { ns = STATUS_NO_MEMORY; break; } ns = NtQuerySystemInformation (SystemProcessInformation, pspi, dSize, &dData); if (ns != STATUS_SUCCESS) { LocalFree (pspi); pspi = NULL; dData = 0; if (ns != STATUS_INFO_LENGTH_MISMATCH) break; } } if (pdData != NULL) *pdData = dData; if (pns != NULL) *pns = ns; return pspi; }