//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1999 - 2000 // // File: processes.cpp // //-------------------------------------------------------------------------- // Processes.cpp: implementation of the CProcesses class. // ////////////////////////////////////////////////////////////////////// #ifndef NO_STRICT #ifndef STRICT #define STRICT 1 #endif #endif /* NO_STRICT */ #include #include #include #include #include "Globals.h" #include "Processes.h" #include "ProcessInfo.h" #include "ProcessInfoNode.h" #include "FileData.h" #include "UtilityFunctions.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CProcesses::CProcesses() { m_fInitialized = false; m_iNumberOfProcesses = 0; m_enumProcessCollectionMethod = NO_METHOD; // Contained Objects m_lpProcessInfoHead = NULL; m_ProcessInfoHeadMutex = NULL; // m_lpProgramOptions = NULL; m_lpModuleInfoCache = NULL; m_lpOutputFile = NULL; m_lpInputFile = NULL; } CProcesses::~CProcesses() { WaitForSingleObject(m_ProcessInfoHeadMutex, INFINITE); // If we have Process Info Objects... nuke them now... if (m_lpProcessInfoHead) { CProcessInfoNode * lpProcessInfoNodePointer = m_lpProcessInfoHead; CProcessInfoNode * lpProcessInfoNodePointerToDelete = m_lpProcessInfoHead; // Traverse the linked list to the end.. while (lpProcessInfoNodePointer) { // Keep looking for the end... // Advance our pointer to the next node... lpProcessInfoNodePointer = lpProcessInfoNodePointer->m_lpNextProcessInfoNode; // Delete the one behind us... delete lpProcessInfoNodePointerToDelete; // Set the node to delete to the current... lpProcessInfoNodePointerToDelete = lpProcessInfoNodePointer; } // Now, clear out the Head pointer... m_lpProcessInfoHead = NULL; } // Be a good citizen and release the Mutex ReleaseMutex(m_ProcessInfoHeadMutex); // Now, close the Mutex if (m_ProcessInfoHeadMutex) { CloseHandle(m_ProcessInfoHeadMutex); m_ProcessInfoHeadMutex = NULL; } } //bool CProcesses::Initialize(CProgramOptions * lpProgramOptions, CModuleInfoCache * lpModuleInfoCache, CFileData * lpInputFile, CFileData * lpOutputFile) bool CProcesses::Initialize(CModuleInfoCache * lpModuleInfoCache, CFileData * lpInputFile, CFileData * lpOutputFile) { // We need the following objects to do business... // if ( lpProgramOptions == NULL || lpModuleInfoCache == NULL) if ( lpModuleInfoCache == NULL) return false; // Let's save away our program options (beats passing it as an // argument to every method...) // m_lpProgramOptions = lpProgramOptions; m_lpInputFile = lpInputFile; m_lpOutputFile = lpOutputFile; m_lpModuleInfoCache = lpModuleInfoCache; m_ProcessInfoHeadMutex = CreateMutex(NULL, FALSE, NULL); if (m_ProcessInfoHeadMutex == NULL) return false; // We only need to grab these exported functions if we intend to // actively query our local machine's processes directly... if (g_lpProgramOptions->GetMode(CProgramOptions::InputProcessesFromLiveSystemMode)) { // PSAPI.DLL API's ARE NOW PREFERRED!! // It doesn't tend to hang when enumerating modules for a process that is being debugged. // The Toolhelp32 APIs seem to hang occasionally taking a snapshot of a process being debugged // and this impacts Exception Monitor (which runs from a script against a process under // windbg) if ( g_lpProgramOptions->IsRunningWindowsNT() ) { // Get the functions for Windows NT 4.0/2000 // Load library and get the procedures explicitly. We do // this so that we don't have to worry about modules using // this code failing to load under Windows 95, because // it can't resolve references to the PSAPI.DLL. if (g_lpDelayLoad->Initialize_PSAPI()) { m_enumProcessCollectionMethod = PSAPI_METHOD; } else { _tprintf(TEXT("Unable to load PSAPI.DLL, which may be required for enumeration of processes.\n")); } } if ( m_enumProcessCollectionMethod == NO_METHOD ) { if (g_lpDelayLoad->Initialize_TOOLHELP32()) { m_enumProcessCollectionMethod = TOOLHELP32_METHOD; } else { _tprintf(TEXT("KERNEL32.DLL is missing required function entry points!!\n")); } } // On Windows NT, we need to enable SeDebugPrivilege to open some processes... if ( ( m_enumProcessCollectionMethod != NO_METHOD ) && g_lpProgramOptions->IsRunningWindowsNT() ) { HANDLE hOurProcessToken = 0; bool fPrivilegeSet = false; // To permit as much access to obtain a process handle as possible, // we need to set the SeDebugPrivilege on our process handle, we can // then open nearly any process... if(OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hOurProcessToken)) { // We got our Process Token... if(SetPrivilege(hOurProcessToken, SE_DEBUG_NAME, TRUE)) { fPrivilegeSet = true; } } if (!fPrivilegeSet) { _tprintf(TEXT("\nWARNING: A required privilege (SeDebugPrivilege) is not held by the user\n")); _tprintf(TEXT("running this program. Due to security, some processes running on this\n")); _tprintf(TEXT("system may not be accessible. An administrator of this machine can grant\n")); _tprintf(TEXT("you this privilege by using User Manager to enable the advanced User Right\n")); _tprintf(TEXT("\"Debug Programs\" to enable complete access to this system.\n")); } if (hOurProcessToken) CloseHandle(hOurProcessToken); } // We are initialized if we were able to enable a Process Collection Method m_fInitialized = ( m_enumProcessCollectionMethod != NO_METHOD ); } else { m_fInitialized = true; } return m_fInitialized; } bool CProcesses::GetProcessesDataForRunningProcessesUsingPSAPI() { LPDWORD lpdwPIDs = NULL; DWORD dwProcessIDHeapSizeUsed, dwProcessIDHeapSize, dwIndex ; CProcessInfo * lpProcessInfo = NULL; bool fRetval = false; if (!m_fInitialized) return false; // It's possible the user provided a PID directly... if so, // we can circumvent the whole search of PIDs on the system... if (g_lpProgramOptions->GetProcessID()) { // Okay, let's create a ProcessInfo object and pass this down to EnumerateModules() lpProcessInfo = new CProcessInfo(); if (lpProcessInfo == NULL) goto error_cleanup; if (!lpProcessInfo->Initialize(m_lpModuleInfoCache, NULL, m_lpOutputFile, NULL)) { goto error_cleanup; } if (lpProcessInfo->EnumerateModules(g_lpProgramOptions->GetProcessID(), this, NULL)) { // Success... add this to the Processes Object... if (!AddNewProcessInfoObject(lpProcessInfo)) { // Failure adding the node... goto error_cleanup; // For now, let's just error on out... } } else { // Failure enumerating modules on the only PID of interest... very bad... goto error_cleanup; } } else { // Nope, we brute force this baby... // Call the PSAPI function EnumProcesses to get all of the // ProcID's currently in the system. // NOTE: In the documentation, the third parameter of // EnumProcesses is named cbNeeded, which implies that you // can call the function once to find out how much space to // allocate for a buffer and again to fill the buffer. // This is not the case. The cbNeeded parameter returns // the number of PIDs returned, so if your buffer size is // zero cbNeeded returns zero. // NOTE: The loop here ensures that we // actually allocate a buffer large enough for all the // PIDs in the system. dwProcessIDHeapSize = 256 * sizeof( DWORD ) ; lpdwPIDs = NULL ; do { if( lpdwPIDs ) { // Hmm.. we've been through this loop already, double the HeapSize and try again. delete [] lpdwPIDs; dwProcessIDHeapSize *= 2 ; } lpdwPIDs = (LPDWORD) new DWORD[dwProcessIDHeapSize]; if( lpdwPIDs == NULL ) { goto error_cleanup; } // Query the system for the total number of processes if( !g_lpDelayLoad->EnumProcesses( lpdwPIDs, dwProcessIDHeapSize, &dwProcessIDHeapSizeUsed ) ) { // It's bad if we can't enum processes... no place to go but to bail out... goto error_cleanup; } } while( dwProcessIDHeapSizeUsed == dwProcessIDHeapSize ); // How many ProcID's did we get? DWORD dwNumberOfPIDs = dwProcessIDHeapSizeUsed / sizeof( DWORD ) ; // Loop through each ProcID. for( dwIndex = 0 ; dwIndex < dwNumberOfPIDs; dwIndex++ ) { // Okay, let's create a ProcessInfo object and pass this down to EnumerateModules() // Each Process gets its own lpProcessInfo = new CProcessInfo(); if (lpProcessInfo == NULL) goto error_cleanup; if (!lpProcessInfo->Initialize(m_lpModuleInfoCache, NULL, m_lpOutputFile, NULL)) { // Failure initializing the ProcessInfo object?!? delete lpProcessInfo; lpProcessInfo = NULL; continue; } if (lpProcessInfo->EnumerateModules(lpdwPIDs[dwIndex], this, NULL)) { // Success... add this to the Processes Object... if (!AddNewProcessInfoObject(lpProcessInfo)) { // Failure adding the node... delete lpProcessInfo; lpProcessInfo = NULL; continue; } // For now, let's error out... } else { // An error enumerating modules might be normal... delete lpProcessInfo; lpProcessInfo = NULL; continue; } } } fRetval = true; goto cleanup; error_cleanup: if (lpProcessInfo) delete lpProcessInfo; cleanup: if (lpdwPIDs) { delete [] lpdwPIDs; } return fRetval; } bool CProcesses::AddNewProcessInfoObject(CProcessInfo * lpProcessInfo) { if (!m_fInitialized) return false; // First, create a ProcessInfoNode object and then attach it to the bottom of the // linked list of nodes... CProcessInfoNode * lpProcessInfoNode = new CProcessInfoNode(lpProcessInfo); /* #ifdef _DEBUG _tprintf(TEXT("Adding Process Info Object for [%s]\n"), lpProcessInfo->m_tszProcessName); #endif */ if (lpProcessInfoNode == NULL) return false; // Couldn't allocate memory.. // Acquire Mutex object to protect the linked-list... WaitForSingleObject(m_ProcessInfoHeadMutex, INFINITE); CProcessInfoNode * lpProcessInfoNodePointer = m_lpProcessInfoHead; if (lpProcessInfoNodePointer) { // Traverse the linked list to the end.. while (lpProcessInfoNodePointer->m_lpNextProcessInfoNode) { // Keep looking for the end... lpProcessInfoNodePointer = lpProcessInfoNodePointer->m_lpNextProcessInfoNode; } lpProcessInfoNodePointer->m_lpNextProcessInfoNode = lpProcessInfoNode; } else { // First time through, the Process Info Head pointer is null... m_lpProcessInfoHead = lpProcessInfoNode; } // Be a good citizen and release the Mutex ReleaseMutex(m_ProcessInfoHeadMutex); InterlockedIncrement(&m_iNumberOfProcesses); return true; } bool CProcesses::SetPrivilege(HANDLE hToken, LPCTSTR Privilege, bool bEnablePrivilege) { TOKEN_PRIVILEGES tp; LUID luid; TOKEN_PRIVILEGES tpPrevious = {0}; DWORD cbPrevious=sizeof(TOKEN_PRIVILEGES); if(!LookupPrivilegeValue( NULL, Privilege, &luid )) return false; // // first pass. get current privilege setting // tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = 0; AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &tpPrevious, &cbPrevious ); if (GetLastError() != ERROR_SUCCESS) return false; // // second pass. set privilege based on previous setting // tpPrevious.PrivilegeCount = 1; tpPrevious.Privileges[0].Luid = luid; if(bEnablePrivilege) { tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED); } else { tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes); } AdjustTokenPrivileges( hToken, FALSE, &tpPrevious, cbPrevious, NULL, NULL ); if (GetLastError() != ERROR_SUCCESS) return false; return true; } bool CProcesses::OutputProcessesData(CollectionTypes enumCollectionType, bool fCSVFileContext, bool fDumpHeader) { // Output to file? if ( !g_lpProgramOptions->GetMode(CProgramOptions::QuietMode) && !g_lpProgramOptions->GetMode(CProgramOptions::PrintTaskListMode) ) { // Output to Stdout? if (!OutputProcessesDataToStdout(enumCollectionType, fCSVFileContext, fDumpHeader)) return false; } // Output to file? if (g_lpProgramOptions->GetMode(CProgramOptions::OutputCSVFileMode)) { // Try and output to file... if (!OutputProcessesDataToFile(enumCollectionType, fDumpHeader)) return false; } if (m_lpProcessInfoHead) { CProcessInfoNode * lpCurrentProcessInfoNode = m_lpProcessInfoHead; while (lpCurrentProcessInfoNode) { // We have a node... print out Process Info for it, then the Modules Data... if (lpCurrentProcessInfoNode->m_lpProcessInfo) { lpCurrentProcessInfoNode->m_lpProcessInfo->OutputProcessData(enumCollectionType, fCSVFileContext, false); } lpCurrentProcessInfoNode = lpCurrentProcessInfoNode->m_lpNextProcessInfoNode; } } return true; } bool CProcesses::OutputProcessesDataToStdout(CollectionTypes enumCollectionType, bool fCSVFileContext, bool fDumpHeader) { if (fDumpHeader) { // Output to stdout... _tprintf(TEXT("\n")); CUtilityFunctions::OutputLineOfStars(); _tprintf(TEXT("%s - Printing Process Information for %d Processes.\n"), g_tszCollectionArray[enumCollectionType].tszCSVLabel, m_iNumberOfProcesses); _tprintf(TEXT("%s - Context: %s\n"), g_tszCollectionArray[enumCollectionType].tszCSVLabel, fCSVFileContext ? g_tszCollectionArray[enumCollectionType].tszCSVContext : g_tszCollectionArray[enumCollectionType].tszLocalContext); CUtilityFunctions::OutputLineOfStars(); } return true; } bool CProcesses::OutputProcessesDataToFile(CollectionTypes enumCollectionType, bool fDumpHeader) { // Don't write anything if there are no processes to report... if (0 == m_iNumberOfProcesses) return true; if (fDumpHeader) { // We skip output of the [PROCESSES] header if -E was specified... if (!g_lpProgramOptions->GetMode(CProgramOptions::ExceptionMonitorMode)) { // Write out the Processes tag so I can detect this output format... if (!m_lpOutputFile->WriteString(TEXT("\r\n")) || !m_lpOutputFile->WriteString(g_tszCollectionArray[enumCollectionType].tszCSVLabel) || !m_lpOutputFile->WriteString(TEXT("\r\n")) ) { _tprintf(TEXT("Failure writing CSV header to file [%s]!"), m_lpOutputFile->GetFilePath()); m_lpOutputFile->PrintLastError(); return false; } } // We have different output for -E if (g_lpProgramOptions->GetMode(CProgramOptions::ExceptionMonitorMode)) { // Write out the header... for the -E option... if (!m_lpOutputFile->WriteString(TEXT("Module Path,Symbol Status,Time/Date String,File Version,Company Name,File Description,File Time/Date String,Local DBG Status,Local DBG,Local PDB Status,Local PDB\r\n"))) { _tprintf(TEXT("Failure writing CSV header to file [%s]!"), m_lpOutputFile->GetFilePath()); m_lpOutputFile->PrintLastError(); return false; } } else { // Write out the Processes Header if (!m_lpOutputFile->WriteString(g_tszCollectionArray[enumCollectionType].tszCSVColumnHeaders)) { _tprintf(TEXT("Failure writing CSV header to file [%s]!"), m_lpOutputFile->GetFilePath()); m_lpOutputFile->PrintLastError(); return false; } } } return true; } bool CProcesses::GetProcessesData() { // Is this being collected interactively? if (g_lpProgramOptions->GetMode(CProgramOptions::InputProcessesFromLiveSystemMode)) { // Invoke the correct Process Collection Method if (GetProcessCollectionMethod() == TOOLHELP32_METHOD) { GetProcessesDataForRunningProcessesUsingTOOLHELP32(); } else if (GetProcessCollectionMethod() == PSAPI_METHOD) { GetProcessesDataForRunningProcessesUsingPSAPI(); } } // Is this being collected from a file? if (g_lpProgramOptions->GetMode(CProgramOptions::InputCSVFileMode)) GetProcessesDataFromFile(); return true; } bool CProcesses::GetProcessesDataFromFile() { CProcessInfo * lpProcessInfo = NULL; char szProcessNameToMatch[_MAX_FNAME+1]; // In case we're matching against these... LPTSTR tszProcessNameToMatch = g_lpProgramOptions->GetProcessName(); DWORD dwProcessIDToMatch = g_lpProgramOptions->GetProcessID(); // If we're going to be matching on a process name, go ahead and grab it // once now, convert to ANSI if necessary (since it comes from the CSV // file in ANSI), and upper case it... if ( ( g_lpProgramOptions->GetMode(CProgramOptions::InputProcessesWithMatchingNameOrPID) ) && ( tszProcessNameToMatch ) ) { // Let's save away the Process Name... CUtilityFunctions::CopyTSTRStringToAnsi(tszProcessNameToMatch, szProcessNameToMatch, _MAX_FNAME+1); // Upper case the process name... we should be ready to match on this now... _strupr(szProcessNameToMatch); } // Read the Process Header Line if (!m_lpInputFile->ReadFileLine()) return false; // Currently, we don't actually read the data... enum { BUFFER_SIZE = 128}; char szProcessName[BUFFER_SIZE]; TCHAR tszProcessName[BUFFER_SIZE]; DWORD iProcessID; // Read the first field (should be blank, unless this is a new collection type if (m_lpInputFile->ReadString()) return true; bool fReturn = true; while (fReturn == true) { // Read the process name... if (0 == m_lpInputFile->ReadString(szProcessName, BUFFER_SIZE)) break; if (!m_lpInputFile->ReadDWORD(&iProcessID)) { fReturn = false; break; } if ( g_lpProgramOptions->GetMode(CProgramOptions::InputProcessesWithMatchingNameOrPID) ) { // Okay, the user has provided us something to match against our data... if ( tszProcessNameToMatch ) { // Process name provided... does it match? if ( strcmp(szProcessNameToMatch, szProcessName) ) { // Nope... well then, we should nuke this line... m_lpInputFile->ReadFileLine(); // Then, jump to the next line processing... goto ReadNewLine; } #ifdef _DEBUG else { printf("DEBUG: MATCH FOUND ON Process Name [%s]\n", szProcessName); } #endif } else { if ( dwProcessIDToMatch != iProcessID ) { // Nope... well then, we should nuke this line... m_lpInputFile->ReadFileLine(); // Then, jump to the next line processing... goto ReadNewLine; } #ifdef _DEBUG else { _tprintf(TEXT("DEBUG: MATCH FOUND ON Process ID [%d]\n"), iProcessID); } #endif } } // Okay, let's create a ProcessInfo object and pass this down to EnumerateModules() // Each Process gets its own lpProcessInfo = new CProcessInfo(); if (lpProcessInfo == NULL) { fReturn = false; break; } if (!lpProcessInfo->Initialize(m_lpModuleInfoCache, m_lpInputFile, m_lpOutputFile, NULL)) { // Failure initializing the ProcessInfo object?!? delete lpProcessInfo; lpProcessInfo = NULL; fReturn = false; break; } // We need to convert this to Unicode possibly... (it will be copied in EnumModules()) CUtilityFunctions::CopyAnsiStringToTSTR(szProcessName, tszProcessName, BUFFER_SIZE); // Save the process name... lpProcessInfo->SetProcessName(tszProcessName); // Enumerate the modules for the process if (!lpProcessInfo->EnumerateModules(iProcessID, this, tszProcessName)) { fReturn = false; break; } // Success... add this to the Processes Object... if (!AddNewProcessInfoObject(lpProcessInfo)) { // Failure adding the node... delete lpProcessInfo; lpProcessInfo = NULL; return false; } ReadNewLine: // Before we read a new line... are we already pointing to the end? if (m_lpInputFile->EndOfFile()) { break; } // Read the first field (should be blank, unless this is a new collection type if (m_lpInputFile->ReadString()) break; } // We don't expect to find anything... return fReturn; } CProcesses::ProcessCollectionMethod CProcesses::GetProcessCollectionMethod() { return m_enumProcessCollectionMethod; } bool CProcesses::GetProcessesDataForRunningProcessesUsingTOOLHELP32() { CProcessInfo * lpProcessInfo = NULL; HANDLE hSnapShot = NULL; bool fReturn = false; if (!m_fInitialized) return false; // It's possible the user provided a PID directly... if so, // we can circumvent the whole search of PIDs on the system... if (g_lpProgramOptions->GetProcessID()) { // Okay, let's create a ProcessInfo object and pass this down to EnumerateModules() lpProcessInfo = new CProcessInfo(); if (lpProcessInfo == NULL) goto error_cleanup; if (!lpProcessInfo->Initialize(m_lpModuleInfoCache, NULL, m_lpOutputFile, NULL)) { goto error_cleanup; } if (lpProcessInfo->EnumerateModules(g_lpProgramOptions->GetProcessID(), this, NULL)) { // Success... add this to the Processes Object... if (!AddNewProcessInfoObject(lpProcessInfo)) { // Failure adding the node... goto error_cleanup; // For now, let's just error on out... } } else { // Failure enumerating modules on the only PID of interest... very bad... goto error_cleanup; } } else { PROCESSENTRY32 procentry; BOOL bFlag; // Get a handle to a Toolhelp snapshot of the systems processes. hSnapShot = g_lpDelayLoad->CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if( hSnapShot == INVALID_HANDLE_VALUE ) { goto error_cleanup ; } // Clear this structure memset(&procentry, 0, sizeof(procentry)); // Get the first process' information. procentry.dwSize = sizeof(PROCESSENTRY32) ; bFlag = g_lpDelayLoad->Process32First( hSnapShot, &procentry ) ; // While there are processes, keep looping. while( bFlag ) { // Okay, let's create a ProcessInfo object and pass this down to EnumerateModules() // Each Process gets its own lpProcessInfo = new CProcessInfo(); if (lpProcessInfo == NULL) goto error_cleanup; if (!lpProcessInfo->Initialize(m_lpModuleInfoCache, NULL, m_lpOutputFile, NULL)) { // Failure initializing the ProcessInfo object?!? delete lpProcessInfo; lpProcessInfo = NULL; // Clear this structure memset(&procentry, 0, sizeof(procentry)); // Get the next Process... procentry.dwSize = sizeof(PROCESSENTRY32) ; bFlag = g_lpDelayLoad->Process32Next( hSnapShot, &procentry ); continue; } // Enumerate the modules for this process... if (lpProcessInfo->EnumerateModules(procentry.th32ProcessID, this, procentry.szExeFile)) { // Success... add this to the Processes Object... if (!AddNewProcessInfoObject(lpProcessInfo)) { // Failure adding the node... delete lpProcessInfo; lpProcessInfo = NULL; } } else { // An error enumerating modules might be normal... delete lpProcessInfo; lpProcessInfo = NULL; } // Get the next Process... procentry.dwSize = sizeof(PROCESSENTRY32) ; bFlag = g_lpDelayLoad->Process32Next( hSnapShot, &procentry ); } } fReturn = true; goto cleanup; error_cleanup: if (lpProcessInfo) delete lpProcessInfo; cleanup: if (hSnapShot != INVALID_HANDLE_VALUE) CloseHandle(hSnapShot); return fReturn; }