Enumerating processes on NT/Win9x






4.94/5 (13 votes)
Nov 23, 2001
4 min read

231470

3505
A simple class encapsulating tlhelp32 and PSAPI
Motivation
Many programmers write software that require the set of running processes to be enumerated. Unfortunately there is no standard method of doing this. On Windows 95 and Windows 98 there are the ToolHelp API functions (found in Kernel32.dll). For some reason the Microsoft NT team didn't like the ToolHelp functions and decided not to add them to Windows NT. Instead they provided their own set of Process Status functions, the PSAPI, and added them to an external module (residing in psapi.dll). Finally, on Windows 2000, the developers chose to provide both methods of enumeration.
What this is
CEnumProcess
is a simple class for enumerating running processes using either PSAPI or ToolHelp. The preferred method is decided on runtime.
It contains two classes: CEnumProcess::CProcessEntry
and CEnumProcess::CModuleEntry
for storing results.
How it works
When creating an instance of the class, it attempts to load the appropriate modules and find PSAPI/ToolHelp related functions. Based on which set of functions it finds the class sets the enumeration method to the most appropriate. For example, this is the code for finding the PSAPI related functions:
// Try to load psapi.dll PSAPI = ::LoadLibrary(TEXT("PSAPI")); if (PSAPI) { // Find PSAPI functions FEnumProcesses = (PFEnumProcesses)::GetProcAddress(PSAPI, TEXT("EnumProcesses")); FEnumProcessModules = (PFEnumProcessModules)::GetProcAddress(PSAPI, TEXT("EnumProcessModules")); #ifdef UNICODE FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, TEXT("GetModuleFileNameExW")); #else FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI, TEXT("GetModuleFileNameExA")); #endif }
Usage
There are seven public functions in the class:int GetAvailableMethods()
int GetSuggestedMethod()
int SetMethod(int method)
BOOL GetProcessNext(CProcessEntry *pEntry)
BOOL GetProcessFirst(CProcessEntry* pEntry)
BOOL GetModuleNext(DWORD dwPID, CModuleEntry* pEntry)
BOOL GetModuleFirst(DWORD dwPID, CModuleEntry* pEntry)
ENUM_METHOD
below:
namespace ENUM_METHOD {const int NONE = 0x0; const int PSAPI = 0x1; const int TOOLHELP= 0x2; const int PROC16 = 0x4; }
ENUM_METHOD::NONE
is used for the unlikely event that no method can be found, like if an NT-user has deleted psapi.dll.
Under Windows 2000, GetAvailableMethods
returns ENUM_METHOD::PSAPI + ENUM_METHOD::TOOLHELP
. The suggested method will be
to use the ToolHelp API. If 16-bit processes should be enumerated ENUM_METHOD::PROC16
should be added to the enumerationmethod. This is usually done
by default.
More interesting are the GetProcess/GetModule functions. They take a pointer to a CProcessEntry
/CModuleEntry
as input, and
returns TRUE
/FALSE
depending on success or failure in enumeration. The useful members of the classes are as follows:
CProcessEntry
LPTSTR lpFilename; // name of file DWORD dwPID; // ID of the process WORD hTask16; // If this is a 16-bit process, // return task handle here, otherwise 0
The member hTask16
is only used on NT and Win2k. If hTask16
is anything other than 0 on these operating systems, this is a 16-bit process. In that case lpFilename
will be the path of the 16-bit process, and
dwPID
will be the identifier of the NTVDM currently run under (see "A WORD on 16-bit processes").
Note that if using ToolHelp under Win2k lpFileName
will not be the full path of the file. Try using GetModuleFirst
to get the first module
loaded (the .exe itself), and then retrive the full path from CModuleEntry
.
CModuleEntry
LPTSTR lpFilename; // path of file PVOID pLoadBase; // Loading address PVOID pPreferredBase; // Address specified in fileheader
All this means you can write code like this, and it will work under both Win9x and NT.
CEnumProcess enumeration; CEnumProcess::CProcessEntry entry; for (BOOL OK = enumeration.GetProcessFirst(&entry); OK; OK = enumeration.GetProcessNext(&entry) ) { // Do something useful TRACE("PID = %X, process = %s\n", entry.dwPID, entry.lpFilename); }
Why the preferred base is important
I have found that many modules does not load at their preferred baseaddress. If a module does not load at its preferred base, the application using it will require more memory and take a performance hit when initializing. This is because a module can only be mapped to its .dll on disk if the baseaddress is equal to the loadaddress. This was the reason for including the preferred base in the moduleentry in the first place. Usually is it due to lazy programmers, who do not rebase their modules from the the default address (0x10000000, or Visual Basic 0x11000000). It could also be hard to predict where a module can find some place to load. The function for finding the preferred base is found below:
PVOID CEnumProcess::GetModulePreferredBase(DWORD dwPID, PVOID pModBase) {if (ENUM_METHOD::NONE == m_method) return NULL; HANDLE hProc = OpenProcess(PROCESS_VM_READ, FALSE, dwPID); if (hProc) {IMAGE_DOS_HEADER idh = {0}; IMAGE_NT_HEADERS inh = {0}; //Read DOS header ReadProcessMemory(hProc, pModBase, &idh, sizeof(idh), NULL); if (IMAGE_DOS_SIGNATURE == idh.e_magic) // DOS header OK? // Read NT headers at offset e_lfanew ReadProcessMemory(hProc, (PBYTE)pModBase + idh.e_lfanew, &inh, sizeof(inh), NULL); CloseHandle(hProc); if (IMAGE_NT_SIGNATURE == inh.Signature) //NT signature OK? // Get the preferred base... return (PVOID) inh.OptionalHeader.ImageBase; } return NULL; //didn't find anything useful.. }For those new to the PE format, these are standard headers of an executable file. When an executable is loaded into memory, the entire file is mapped - even the headers. A lot of useful information can be found here, like the size of the mapped file.
A WORD on 16-bit processes
Under Windows 95 and Windows 98 ToolHelp API treats 16-bit applications just like any other process. This is not the case under NT. InsteadCEnumProcess
will return the name of the NT virtual DOS machine (NTVDM) it is currently run under. This means that
if you run the testapplication and find some processnames like "ntvdm.exe", you have found a 16-bit process. If ENUM_METHOD::PROC16
is currently set, the next entries returned by GetNextProcess
will be the 16-bit processes that currently run in this virtual machine.
This is done by using the VDMDBG API - a set of functions for debugging 16-bit applications. VDMDBG allows enumeration of modules, but only if the process is acting as a debugger. This require that the 16-bit debugger WOWDEB.EXE task runs in the
VM currently debugged. Attaching as a debugger is not a very good idea, though. There is no way of unattaching and if
the debugger process terminates so will the debugee. For that reason enumeration of 16-bit modules is not included in the class.
Known bugs/limitations
Some processes has a sequrity attribute that prevents reading its memory. This means that modules can not be enumerated. If using PSAPI, not even the filename can be retrieved.
Contact
Send suggestions, improvements and comments to me.History
10 Sep 2002 - updated downloads