線程操做與進程掛起(Windows核心編程)

0x01 線程掛起與切換

  • 對於掛起進程,掛起線程則比較簡單,利用 ResumeThread 與 SuspendThread 便可,當線掛起時線程用戶狀態下的一切操做都會暫停
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;

unsigned __stdcall ThreadFun1(void *pvParam);

int main(int argc, char *argv[])
{
	DWORD ThreadId1 = NULL;
	HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1);

	// 掛起進程
	SuspendThread(MyThread1);
	cout << "線程開始掛起" << endl;

	// 從新釋放線程,也就是運行掛起的線程
	ResumeThread(MyThread1);
	ResumeThread(MyThread1);

	WaitForSingleObject(MyThread1, INFINITE);
	
	CloseHandle(MyThread1);
	return 0;
}

unsigned __stdcall ThreadFun1(void *pvParam)
{
	cout << "ThreadFun1" << endl;
	return true;
}
  • 爲何進行了兩次 ResumeThread 操做呢,是由於 CREATE_SUSPENDED 也會將線程內核計數加 1 變爲了 2
    在這裏插入圖片描述
  • 使用 SwitchToThread 切換線程
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;

unsigned __stdcall ThreadFun1(void *pvParam);
unsigned __stdcall ThreadFun2(void *pvParam);

int main(int argc, char *argv[])
{
	DWORD ThreadId1 = NULL; DWORD ThreadId2 = NULL;

	// 建立兩個線程,並運行
	HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1); ResumeThread(MyThread1);
	HANDLE MyThread2 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun2, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId2); ResumeThread(MyThread2);
	
	// 等待線程關閉
	WaitForSingleObject(MyThread1, INFINITE); WaitForSingleObject(MyThread2, INFINITE);

	// 關閉句柄
	CloseHandle(MyThread1); CloseHandle(MyThread2);
	return 0;
}

unsigned __stdcall ThreadFun1(void *pvParam)
{
	cout << " 切換 ThreadFun2 " << endl;
	SwitchToThread();
	cout << " ThreadFun1 is start! " << endl;
	return true;
}
unsigned __stdcall ThreadFun2(void *pvParam)
{
	cout << " ThreadFun2 is start! " << endl;
	cout << " 切換 ThreadFun1 " << endl;
	Sleep(200);
	SwitchToThread();
	return true;
}
  • 其實 SwitchToThread 的根本做用就是 CPU 資源讓給其餘的線程
    在這裏插入圖片描述

0x02 掛起進程

  • Windows 並無給出相應的內核 API 函數來掛起某一個進程,想要掛起進程就必須遍歷進程中的全部運行中的線程而且將它們掛起,和掛起線程相似,只不過須要操做多個線程,示例以下:
#include <Windows.h>
#include <iostream>
#include <process.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <strsafe.h>
using namespace std;

unsigned __stdcall ThreadFun1(void *pvParam);
unsigned __stdcall ThreadFun2(void *pvParam);
unsigned __stdcall ThreadFun3(void *pvParam);
DWORD WINAPI ControlProThreads(DWORD ProcessId, BOOL Suspend);
VOID WINAPI ErrorCodeTransformation(DWORD ErrorCode);

int main(int argc, char *argv[])
{
	// 建立 3 個掛起線程 MyThread一、MyThread二、MyThread3,掛起計數爲 1
	DWORD ThreadId1 = NULL, ThreadId2 = NULL, ThreadId3 = NULL;
	HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1);
	HANDLE MyThread2 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun2, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId2);
	HANDLE MyThread3 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun3, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId3);

	// 將掛起計數 -1 變爲 0,表示運行線程
	ResumeThread(MyThread1); ResumeThread(MyThread2); ResumeThread(MyThread3);
	
	// 獲取當前進程的 ID 保存在 ProcessId 中
	HANDLE DupProcessHandle = NULL; DWORD ProcessId;
	DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
	ProcessId = GetProcessId(DupProcessHandle);

	// 用於掛起進程的函數,參數一爲須要掛起的進程 ID,參數而表示是否爲掛起操做,反之則爲釋放操做
	DWORD res = ControlProThreads(ProcessId, TRUE);

	// 若調用失敗,打印錯誤信息
	if (res != TRUE) ErrorCodeTransformation(res);

	// 等待全部線程退出
	HANDLE Threads[3]; Threads[0] = MyThread1; Threads[1] = MyThread2; Threads[2] = MyThread3;
	WaitForMultipleObjects(3, Threads, TRUE, INFINITE);

	// 關閉句柄
	CloseHandle(MyThread1); CloseHandle(MyThread2); CloseHandle(MyThread3);
	return 0;
}

DWORD WINAPI ControlProThreads(DWORD ProcessId, BOOL Suspend)
{
	// 定義函數錯誤代碼
	DWORD LastError = NULL;

	// 經過 CreateToolhelp32Snapshot 函數獲取當前系統全部線程的快照
	HANDLE ProcessHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, ProcessId);
	if (ProcessHandle == INVALID_HANDLE_VALUE)
	{
		// 函數調用失敗,返回錯誤代碼,方便打印出錯誤信息
		LastError = GetLastError();
		return LastError;
	}

	// 判斷進程是否爲當前進程,SkipMainThread 的做用是是否跳過主線程
	INT SkipMainThread = 0; HANDLE DupProcessHandle = NULL;
	DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &DupProcessHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
	DWORD IsThisProcess = GetProcessId(DupProcessHandle);

	// 若是傳入的進程 ID 不是當前進程,則將 SkipMainThread 變爲 1,表示不跳過主線程
	if (ProcessId != IsThisProcess) SkipMainThread = 1;

	THREADENTRY32 ThreadInfo = { sizeof(ThreadInfo) };
	BOOL fOk = Thread32First(ProcessHandle, &ThreadInfo);

	while (fOk)
	{
		// 將線程 ID 轉換爲線程句柄
		HANDLE ThreadHandle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadInfo.th32ThreadID);

		// 是否爲傳入進程的子線程,且判斷是掛起操做仍是釋放操做
		if (ThreadInfo.th32OwnerProcessID == ProcessId && Suspend == TRUE)
		{
			// 若是是當前進程,就跳過主線程,SkipMainThread == 0 表示爲主線程
			if (SkipMainThread != 0)
			{
				// 掛起線程
				DWORD count = SuspendThread(ThreadHandle);
				cout << "[*] 線程號: " << ThreadInfo.th32ThreadID << " 掛起係數 + 1 " << " 上一次掛起係數爲: " << count << endl;
			}
			SkipMainThread++;
		}
		else if(ThreadInfo.th32OwnerProcessID == ProcessId && Suspend == FALSE)
		{
			if (SkipMainThread != 0)
			{
				// 釋放線程
				DWORD count = ResumeThread(ThreadHandle);
				cout << "[-] 線程號: " << ThreadInfo.th32ThreadID << " 掛起係數 - 1 " << " 上一次掛起係數爲: " << count << endl;
			}
			SkipMainThread++;
		}
		fOk = Thread32Next(ProcessHandle, &ThreadInfo);
	}

	// 關閉句柄
	CloseHandle(ProcessHandle);
}

unsigned __stdcall ThreadFun1(void *pvParam)
{
	cout << "ThreadFun1" << endl;
	// 睡眠 2000 毫秒,模擬線程正在執行工做
	Sleep(2000);
	return true;
}
unsigned __stdcall ThreadFun2(void *pvParam)
{
	cout << "ThreadFun2" << endl;
	// 睡眠 2000 毫秒,模擬線程正在執行的工做
	Sleep(2000);
	return true;
}
unsigned __stdcall ThreadFun3(void *pvParam)
{
	cout << "ThreadFun3" << endl;
	// 睡眠 2000 毫秒,模擬線程正在執行的工做
	Sleep(2000);
	return true;
}

// 若是返回錯誤,可調用此函數打印詳細錯誤信息
VOID WINAPI ErrorCodeTransformation(DWORD ErrorCode)
{
	LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = ErrorCode;
	// 將錯誤代碼轉換爲錯誤信息
	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL
	);
	lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + 40) * sizeof(TCHAR));
	StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf), TEXT("錯誤代碼 %d : %s"), dw, lpMsgBuf);
	// 彈窗顯示錯誤信息
	MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
	LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); ExitProcess(dw);
}
  • 值得注意的是,ControlProThreads 函數會判斷傳入的進程 ID 是否爲當前進程 ID,若是是當前進程 則跳過主線程,不然主線程被掛起,全部的操做都會暫停,至關於一種特殊的死鎖
  • 而 ErrorCodeTransformation 函數則是將錯誤代碼轉換爲錯誤信息,方便查找出錯誤,在使用內核 API 進行編程時尤爲須要注意這一點
  • 固然 ControlProThreads 運行起來也會有必定的風險,由於獲取的快照以後可能會有新線程建立,也會有舊進程銷燬;以下圖所示,掛起了除了主線程外的當前進程的全部線程
    在這裏插入圖片描述
  • 若是須要釋放掛起的進程,只須要將 FALSE 傳遞給 ControlProThreads 的第二個參數便可
// 用於掛起進程的函數,參數一爲須要掛起的進程 ID,參數而表示是否爲掛起操做,反之則爲釋放操做
	ControlProThreads(ProcessId, TRUE);
	Sleep(2000);
	cout << "\n 睡眠 2000 毫秒 \n" << endl;
	ControlProThreads(ProcessId, FALSE);
  • 這樣就至關於暫停線程再運行線程,相應的假如屢次掛起,則須要對應屢次釋放操做,線程內核計數變爲 0 的時候就能夠被 CUP 調度了
    在這裏插入圖片描述

0x03 線程(進程)執行時間

  • 想要獲取進程和線程的時間信息,可使用微軟提供的 GetThreadTime 和 GetProcessTime 這兩個函數
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;

unsigned __stdcall ThreadFun1(void *pvParam);

int main(int argc, char *argv[])
{
	// 建立進程
	TCHAR CmdLine[8] = TEXT("CMD.EXE"); 
	STARTUPINFO StartInfo = { sizeof(StartInfo) }; PROCESS_INFORMATION ProcessInfo = { 0 };
	CreateProcess(NULL, CmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &StartInfo, &ProcessInfo);
	WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
	
	// 獲取進程執行的時間
	FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;
	GetProcessTimes(ProcessInfo.hProcess, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);
	cout << "hProcess 所用的內核時間爲: " << ftKernelTime.dwLowDateTime / 10000 << " - hProcess 所用的用戶時間爲: " << ftUserTime.dwLowDateTime / 10000 << endl;

	// 建立線程
	DWORD ThreadId1 = NULL;
	HANDLE MyThread1 = (HANDLE)_beginthreadex(NULL, NULL, ThreadFun1, NULL, CREATE_SUSPENDED, (unsigned *)&ThreadId1); ResumeThread(MyThread1);
	WaitForSingleObject(MyThread1, INFINITE);

	// 獲取線程的執行時間
	GetThreadTimes(MyThread1, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);
	cout << "MyThread1 所用的內核時間爲: " << ftKernelTime.dwLowDateTime / 10000  << " - MyThread1 所用的用戶時間爲: " << ftUserTime.dwLowDateTime / 10000 << endl;

	// 關閉句柄
	CloseHandle(MyThread1);
	return 0;
}

unsigned __stdcall ThreadFun1(void *pvParam)
{
	Sleep(100);
	return true;
}
  • 運行以下所示,仍是有一點不許確的
    在這裏插入圖片描述
  • 要想得到更準確的運行時間能夠這麼辦,利用 QueryPerformanceFrequency 配合 QueryPerformanceCounter 就能夠達到更爲精確的運行時間
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;

int main(int argc, char *argv[])
{
	LARGE_INTEGER nFreq; LARGE_INTEGER nBeginTime; LARGE_INTEGER nEndTime; double time;
	QueryPerformanceFrequency(&nFreq);
	
	// 開始計時
	QueryPerformanceCounter(&nBeginTime);

	// 建立進程
	TCHAR CmdLine[8] = TEXT("CMD.EXE"); 
	STARTUPINFO StartInfo = { sizeof(StartInfo) }; PROCESS_INFORMATION ProcessInfo = { 0 };
	CreateProcess(NULL, CmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &StartInfo, &ProcessInfo);
	WaitForSingleObject(ProcessInfo.hProcess, INFINITE);

	// 結束及時
	QueryPerformanceCounter(&nEndTime);
	time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
	cout << "進程運行時間爲: " << time << " 秒 " << endl;
	
	return 0;
}
  • 相比 GetThreadTime 和 GetProcessTime 這兩個函數,確實精確了不少,緣由是因爲計時的線程是非搶佔式的,執行完之後纔會執行其餘線程
    在這裏插入圖片描述
相關文章
相關標籤/搜索