Geoff Chappell, Software Analyst
The SYSTEM_PROCESS_INFORMATION structure is what a successful call to ZwQuerySystemInformation or NtQuerySystemInformation produces at the start of its output buffer and irregularly throughout the buffer when given the information class SystemProcessInformation (0x05), SystemExtendedProcessInformation (0x39) or SystemFullProcessInformation (0x94).
These information classes produce descriptions not just of the running processes but also of those processes’ threads. A quick summary is that the information for each process is:
The totality of the output for all processes is a sequence of these variable-size sets, one per process. In each set, the NextEntryOffset member at the beginning of the SYSTEM_PROCESS_INFORMATION tells how many bytes to advance from that SYSTEM_PROCESS_INFORMATION to the next, or is zero in the last.
The SYSTEM_PROCESS_INFORMATION structure is defined in WINTERNL.H from the Software Development Kit (SDK). The definition there is greatly reduced, defining just the NextEntryOffset, UniqueProcessId, HandleCount, PeakPagefileUsage and PrivatePageCount members. Documentation of NtQuerySystemInformation describes the SystemProcessInformation case as returning “an array of SYSTEM_PROCESS_INFORMATION structures, one for each process running in the system”, with no mention of what information lies in between. Separate documentation of ZwQuerySystemInformation presents a SYSTEM_PROCESS_INFORMATION that differs slightly from the WINTERNL.H definition in showing the NumberOfThreads member too, again without noting the connection with information between the “documented” structures.
Microsoft does publish the practical equivalent of a C-language definition as type information in public symbol files, though not for the kernel, where the structure is prepared, nor even for low-level user-mode DLLs that interpret the structure, but for various higher-level user-mode DLLs such as URLMON.DLL and only then starting with version 6.2.
Two earlier disclosures of type information are known, though not in symbol files but in statically linked libraries: GDISRVL.LIB from the Device Driver Kit (DDK) for Windows NT 3.51; and SHELL32.LIB from the DDK for Windows NT 4.0.
Since version 5.0, the SYSTEM_PROCESS_INFORMATION is 0xB8 or 0x0100 bytes in 32-bit and 64-bit Windows, respectively. The original structure is 0x88 bytes. The change can ultimately be traced to a problem that early versions had with the I/O counters. Though version 3.10 provided for 64-bit counts of bytes transferred, it allowed only 32 bits for counting operations. The need to widen the latter to 64 bits each seems to have caused them all to be dropped for several versions. It is not just that they are not retrievable in this structure (or through the ProcessIoCounters case of NtQueryInformationProcess), but that the kernel stops maintaining them. When they were brought back for version 5.0, all now 64-bit, they were appended. Not until version 6.1 is the space that had been occupied by the 32-bit counts fully reassigned.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
ULONG NextEntryOffset; |
all | |
0x04 | 0x04 |
ULONG NumberOfThreads; |
all | |
0x08 |
LARGE_INTEGER ReadTransferCount; |
3.10 only | later at 0xA0 | |
0x08 |
LARGE_INTEGER SpareLi1; |
3.50 to 5.2 | ||
LARGE_INTEGER WorkingSetPrivateSize; |
6.0 and higher | |||
0x10 |
LARGE_INTEGER WriteTransferCount; |
3.10 only | later at 0xA8 | |
0x10 |
LARGE_INTEGER SpareLi2; |
3.50 to 6.0 | ||
ULONG HardFaultCount; |
6.1 and higher | |||
0x14 | 0x14 |
ULONG NumberOfThreadsHighWatermark; |
6.1 and higher | |
0x18 |
LARGE_INTEGER OtherTransferCount; |
3.10 only | later at 0xB0 | |
0x18 |
LARGE_INTEGER SpareLi3; |
3.50 to 6.0 | ||
ULONGLONG CycleTime; |
6.1 and higher | |||
0x20 | 0x20 |
LARGE_INTEGER CreateTime; |
all | |
0x28 | 0x28 |
LARGE_INTEGER UserTime; |
all | |
0x30 | 0x30 |
LARGE_INTEGER KernelTime; |
all | |
0x38 | 0x38 |
UNICODE_STRING ImageName; |
all | |
0x40 | 0x48 |
LONG BasePriority; |
all | |
0x44 | 0x50 |
PVOID UniqueProcessId; |
all | |
0x48 | 0x58 |
PVOID InheritedFromUniqueProcessId; |
all | |
0x4C |
ULONG ReadOperationCount; |
3.10 only | later as LARGE_INTEGER at 0x88 | |
ULONG SpareUl1; |
3.50 only | |||
0x60 |
ULONG HandleCount; |
3.51 and higher | ||
0x50 |
ULONG WriteOperationCount; |
3.10 only | later as LARGE_INTEGER at 0x90 | |
ULONG SpareUl2; |
3.50 to 4.0 | |||
0x64 |
ULONG SessionId; |
5.0 and higher | ||
0x54 |
ULONG OtherOperationCount; |
3.10 only | later as LARGE_INTEGER at 0x98 | |
ULONG SpareUl3; |
3.50 to 5.0 | |||
0x68 |
ULONG_PTR UniqueProcessKey; |
5.1 and higher | ||
0x58 | 0x70 |
ULONG_PTR PeakVirtualSize; |
all | |
0x5C | 0x78 |
ULONG_PTR VirtualSize; |
all | |
0x60 | 0x80 |
ULONG PageFaultCount; |
all | |
0x64 | 0x88 |
ULONG_PTR PeakWorkingSetSize; |
all | |
0x68 | 0x90 |
ULONG_PTR WorkingSetSize; |
all | |
0x6C | 0x98 |
ULONG_PTR QuotaPeakPagedPoolUsage; |
all | |
0x70 | 0xA0 |
ULONG_PTR QuotaPagedPoolUsage; |
all | |
0x74 | 0xA8 |
ULONG_PTR QuotaPeakNonPagedPoolUsage; |
all | |
0x78 | 0xB0 |
ULONG_PTR QuotaNonPagedPoolUsage; |
all | |
0x7C | 0xB8 |
ULONG_PTR PagefileUsage; |
all | |
0x80 | 0xC0 |
ULONG_PTR PeakPagefileUsage; |
all | |
0x84 | 0xC8 |
ULONG_PTR PrivatePageCount; |
all | last member in 3.10 to 4.0 |
0x88 | 0xD0 |
LARGE_INTEGER ReadOperationCount; |
5.0 and higher | earlier as ULONG at 0x4C |
0x90 | 0xD8 |
LARGE_INTEGER WriteOperationCount; |
5.0 and higher | earlier as ULONG at 0x50 |
0x98 | 0xE0 |
LARGE_INTEGER OtherOperationCount; |
5.0 and higher | earlier as ULONG at 0x54 |
0xA0 | 0xE8 |
LARGE_INTEGER ReadTransferCount; |
5.0 and higher | earlier at 0x08 |
0xA8 | 0xF0 |
LARGE_INTEGER WriteTransferCount; |
5.0 and higher | earlier at 0x10 |
0xB0 | 0xF8 |
LARGE_INTEGER OtherTransferCount; |
5.0 and higher | earlier at 0x18 |
The name SpareUl1 is an invention. Microsoft’s names SpareUl2 and SpareUl3 for the other 32-bit counts that were discontinued for version 3.50 survive in the GDISRVL.LIB for version 3.51 but the first in the sequence had already been ressigned as the HandleCount.
Whatever may be suggested to the contrary by its name, the PrivatePageCount is of bytes, just like the other counters whose names talk of size or usage.
Some of the SYSTEM_PROCESS_INFORMATION members are specially important for describing the variable-size data that continues the process’s description.
Immediately following the SYSTEM_PROCESS_INFORMATION is an array of zero or more SYSTEM_THREAD_INFORMATION structures if the information class is SystemProcessInformation, else SYSTEM_EXTENDED_THREAD_INFORMATION structures. Either way, the NumberOfThreads member tells how many.
The process’s name may be present as a null-terminated Unicode string. Its address and size are in the ImageName member. This is the full name if the information class is SystemFullProcessInformation. For the older information classes, this name is an extract, beginning after the last backslash.
The UniqueProcessKey is undefined for SystemProcessInformation. For the newer information classes it originally revealed the page number of the process’s page directory base. Version 6.0 instead reveals the address of the EPROCESS structure that represents the process as a kernel object. Whether the member was named UniqueProcessKey in these versions is not known. Whatever it was named, what it contained may have been thought to disclose too much: since version 6.1 the UniqueProcessKey is set identically to the UniqueProcessId.