前言
會開始研究C++去控制Process的原因,是因為專案中用到的Selenium會無預警的Hang住,接著工作都無法繼續執行下去。在Linux的部分,我寫了一個Script去kill它。但Windows的部分該怎麼辦呢? 其實可以透過taskkill的指令。Selenium是透過Java去啟動執行的,因此它依賴於Java Process(java.exe)。但如果我們輸入taskkill /im java.exe,相信會發生你不想看到的事情。因此我們該如何取得Selenium的Java Process呢?其實只要能夠取得執行參數就能做到。小弟所購買的Windows程式設計,僅教導如何取得到Process列表。經過Google後,看到一套叫ProcessExplorer的軟體。
讓我篤信:一定可以辦到的。(OS: Google大神提供的方法很多都是C#的…) 最後終於讓我找到歪外國人寫的API,透過fetch process heap去取得command line。
分析
GetNtProcessInfo所提供的方法BOOL sm_GetNtProcessInfo(const DWORD dwPID, smPROCESSINFO *ppi),我們必須提供一個pid給它。因此我們需要實作以下內容:
- 從所有process中取得java process list。
- 將每一個java process透過sm_GetNtProcessInfo去取得各別的cmd line。
- 取得cmd line中有出現selenium字眼的java process 。
- 刪除掉它!
以上步驟,我們可以學習到:
- 取得Process list。
- 如何使用sm_GetNtProcessInfo API。
- 刪除Process list。
看起來雖然才三個項目,但這之中蘊含著無數的精華!
實作
ProcessUtil
首先我實作了一個ProcessUtil類別,它提供取得ProcEntry列表(getProcEntryList)、根據Process名稱取得ProcEntry列表(findProcEntryList)、根據pid取得ProcEntry(findProcEntry)與終結行程(terminateProcess)。以前在寫作業的時候,傳入字串的參數不是用string就是用char*。在看了Charles(這名字好眼熟?)的Windows程式設計後,得知Windows使用wchar去解決Unicode的問題。使用TCHAR是比較好的寫法,TCHAR的宣告可透過前處理器去決定用wchar還是char。像跨平台的程式碼,型態的宣告也會透過前處理器去決定。
#include "stdafx.h" #include <list> #include <map> using namespace std; class ProcessUtil { public: static list<PROCESSENTRY32> getProcEntryList(); static list<PROCESSENTRY32> findProcEntryList(_TCHAR* aProcName); static PPROCESSENTRY32 findProcEntry(DWORD aPid); static BOOL terminateProcess(DWORD aPid); private: static map<DWORD,PROCESSENTRY32> getProcEntryWithPidMap(); };
getProcEntryWithPidMap將會回傳一個以pid為key,procEntry為value的map。實作原理是透過CreateToolhelp32Snapshot建立Process的Snapshot,接著透過Process32First找尋第一個Process,最後透過Process32Next將所有的Process資訊塞入map中。基本上書上或網路上找到的code都大同小異,這裡我把它做成map為了便於之後的search。
map<DWORD,PROCESSENTRY32> ProcessUtil::getProcEntryWithPidMap(){ map<DWORD,PROCESSENTRY32> pidPeMap; HANDLE hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); if( hProcessSnap == INVALID_HANDLE_VALUE ) { return pidPeMap; } PROCESSENTRY32 pe32; pe32.dwSize = sizeof( PROCESSENTRY32 ); if( !Process32First( hProcessSnap, &pe32 ) ){ CloseHandle( hProcessSnap ); return pidPeMap; } do { pidPeMap.insert( pair<DWORD,PROCESSENTRY32>(pe32.th32ProcessID, pe32) ); } while( Process32Next( hProcessSnap, &pe32 ) ); CloseHandle( hProcessSnap ); return pidPeMap; }
剩下的三個method沒什麼特別的。getProcEntryList是透過getProcEntryWithPidMap實作;findProcEntryList透過wcscmp比對名稱;findProcEntry透過map去找到對應的value。這裡就和吃飯一樣容易。
list<PROCESSENTRY32> ProcessUtil::getProcEntryList(){ list<PROCESSENTRY32> porcEntryList; map<DWORD,PROCESSENTRY32> pidPeMap = getProcEntryWithPidMap(); map<DWORD,PROCESSENTRY32>::iterator it; for ( it=pidPeMap.begin() ; it != pidPeMap.end(); it++ ){ porcEntryList.push_back((*it).second); } return porcEntryList; } list<PROCESSENTRY32> ProcessUtil::findProcEntryList(_TCHAR* aProcName){ list<PROCESSENTRY32> porcEntryList; map<DWORD,PROCESSENTRY32> pidPeMap = getProcEntryWithPidMap(); map<DWORD,PROCESSENTRY32>::iterator it; for ( it=pidPeMap.begin() ; it != pidPeMap.end(); it++ ){ if(!wcscmp(aProcName, (*it).second.szExeFile)){ porcEntryList.push_back((*it).second); } } return porcEntryList; } PPROCESSENTRY32 ProcessUtil::findProcEntry(DWORD aPid){ map<DWORD,PROCESSENTRY32> pidPeMap = getProcEntryWithPidMap(); map<DWORD,PROCESSENTRY32>::iterator it= pidPeMap.find(aPid); if( it == pidPeMap.end() ) return NULL; return &it->second; } BOOL ProcessUtil::terminateProcess(DWORD aPid){ HANDLE hProc = OpenProcess(PROCESS_TERMINATE, BOOL(0), aPid); if( hProc ) { TerminateProcess( hProc,0); return CloseHandle(hProc); } return false; }
有了以上的code,我們就可以輕易的取到process的pid和終結行程。
Main
這裡實作以下幾個function:確認行程的Command line是否吻合(isMatchProcess)、終結所有名稱吻合的行程(terminalAllProcess)與終結所有名稱吻合且command line也吻合的行程(terminalMatchProcess)。基本上就是先透過ProcessUtil::findProcEntryList找到名稱吻合的行程列表,接著再透過參考文獻二的API去取得command line做比對。
#include "stdafx.h" #include "stdafx.h" #include "NTProcessInfo.h" #include <vector> #include <iostream> #include <wchar.h> using namespace std; #include "ProcessUtil.h" #ifdef _UNICODE #define tcout wcout #else #define tcout cout #endif enum Action{ EXIT, DEL_ALL, DEL_CHK }; BOOL isMatchProcess(DWORD aPid, _TCHAR* aCmdLine){ smPROCESSINFO procInfo; sm_GetNtProcessInfo( aPid, &procInfo); return wcswcs(procInfo.szCmdLine, aCmdLine) != 0; } void terminalAllProcess(list<PROCESSENTRY32> aProcList){ list<PROCESSENTRY32>::iterator it; for ( it=aProcList.begin() ; it != aProcList.end(); it++ ){ ProcessUtil::terminateProcess(it->th32ProcessID); } } void terminalMatchProcess(list<PROCESSENTRY32> aProcList, _TCHAR* aCmdLine){ list<PROCESSENTRY32>::iterator it; for ( it=aProcList.begin() ; it != aProcList.end(); it++ ){ if( isMatchProcess(it->th32ProcessID, aCmdLine)) { tcout << "Terminal processes: " << it->th32ProcessID << endl; ProcessUtil::terminateProcess(it->th32ProcessID); } } } int _tmain(int argc, _TCHAR* argv[]) { // check action. Action action; switch(argc){ case 2: action = DEL_ALL; break; case 3: action = DEL_CHK; break; default: action = EXIT; break; } // exit if( action == EXIT ){ return EXIT_SUCCESS; } // probe and delete process list<PROCESSENTRY32> foundProcList = ProcessUtil::findProcEntryList(argv[1]); int foundProcNumber = foundProcList.size(); if( !foundProcNumber ){ tcout << "Not found any process!" << endl; return EXIT_SUCCESS; } else { tcout << "Found " << foundProcNumber << " " << argv[1] << " processes." << endl; } if( action == DEL_ALL ) { tcout << "Terminal all processes." << endl; terminalAllProcess(foundProcList); } else if( action == DEL_CHK ){ HMODULE hNtDll = sm_LoadNTDLLFunctions(); if(!hNtDll){ cout << "Load ntdll failed!" << endl; return EXIT_FAILURE; } sm_EnableTokenPrivilege(SE_DEBUG_NAME); terminalMatchProcess(foundProcList, argv[2]); sm_FreeNTDLLFunctions(hNtDll); } return EXIT_SUCCESS; } #include "NTProcessInfo.cpp"
這裡需要注意的是:在使用sm_GetNtProcessInfoLL之前,必須先透過sm_LoadNTDLLFunctions去Load NTDLL,接著再去EnableTokenPrivilege,最後用完要去清掉它。
HMODULE hNtDll = sm_LoadNTDLLFunctions(); if(!hNtDll){ cout << "Load ntdll failed!" << endl; return EXIT_FAILURE; } sm_EnableTokenPrivilege(SE_DEBUG_NAME); terminalMatchProcess(foundProcList, argv[2]); sm_FreeNTDLLFunctions(hNtDll);
執行結果
如下圖所示,輸入參數為java.exe selenium。四個java processes中,只會刪除與selenium有關的,執行結束後剩下三個java processes。
參考文獻
- Taking a Snapshot and Viewing Processes
- Get Process Info with NtQueryInformationProcess
- Windows程式設計 - 王艷平 編著
- Windows程式設計 第五版 - Charles Petzold 原著, 余孟學 編譯
留言
張貼留言