3环模拟线程切换

c++

Posted by YiMiTuMi on May 31, 2021

3环模拟线程切换

线程切换实际就是切换保存线程的堆栈。

线程切换参数

结构体仿写 _EHREAD 中基本的字段。

//线程结构体(_EHREAD)
typedef struct _IM_SWITCH_THREAD
{
	char* name;      //线程名
	int iFlage;      //线程状态
	int SleepTime;   //休眠时间

	void* InitialStack; //线程堆栈起始的位置
	void* StackLimit;   //线程堆栈界限
	void* KernelStack;  //线程堆栈当前的位置,即ESP

	void* lpParameter;  //线程函数的参数
	void (*func)(void* lpParameterFunction); //线程函数

}IM_SWITCH_THREAD, *PIM_SWITCH_THREAD;

在window内核中线程是根据状态分开存放的,这里用一个数组加标志位存放所有的线程状态。

//保存线程数组
static IM_SWITCH_THREAD ImThreadList_My[THREAD_MAX_COUNT] = { 0 };

用来保存当前运行线程的编号:

static int iCurrentThreadIndex = 0;

线程初始化堆栈函数

在创建一个一个线程时,我们要给线程初始化一个堆栈,用来存放线程数据,并给结构体赋值。

//初始化堆栈
void initImThread(PIM_SWITCH_THREAD ImThreadInfo, char* name, void (*func)(void* lpParameterFunction), void* lpParameter)
{
	unsigned char* StackPages;
	unsigned int* StackDwordParam;

	//申请空间用来保存线程栈(申请物理页)
	StackPages = (unsigned char*)VirtualAlloc(NULL, STACK_SIZE, MEM_COMMIT, PAGE_READWRITE);

	//初始化内存
	memset(StackPages, 0, STACK_SIZE);

	//填充结构体 , 这里要模拟堆栈的数据
	ImThreadInfo->iFlage = THREAD_CREAD;
	ImThreadInfo->InitialStack = StackPages + STACK_SIZE;  
	ImThreadInfo->StackLimit = StackPages;
	ImThreadInfo->name = name;
	ImThreadInfo->func = func;
	ImThreadInfo->lpParameter = lpParameter;

	StackDwordParam = (unsigned int*)(StackPages + STACK_SIZE);

	//入栈
	PushStack(&StackDwordParam, (unsigned int)lpParameter); //压入函数参数
	PushStack(&StackDwordParam, (unsigned int)7);           //随便写一个参数,用来平衡堆栈
	PushStack(&StackDwordParam, (unsigned int)ThreadStartUp); //线程入口
	PushStack(&StackDwordParam, (unsigned int)0);     //push ebp
	PushStack(&StackDwordParam, (unsigned int)0);	  //push edi
	PushStack(&StackDwordParam, (unsigned int)0);	  //push esi
	PushStack(&StackDwordParam, (unsigned int)0);     //push ebx
	PushStack(&StackDwordParam, (unsigned int)0);     //push ecx
	PushStack(&StackDwordParam, (unsigned int)0);     //push edx
	PushStack(&StackDwordParam, (unsigned int)0);     //push eax

	ImThreadInfo->KernelStack = StackDwordParam;   //栈顶

	return;
}

线程切换函数

实际上线程切换就只切换了线程的堆栈,即esp。

__declspec(naked) void SwitchThreadContext(PIM_SWITCH_THREAD pCurrentThread, PIM_SWITCH_THREAD pNewThread)
{
	_asm
	{
		push ebp
		mov ebp, esp  //提升堆栈

		push edi
		push esi
		push ebx
		push ecx
		push edx
		push eax

		mov esi, pCurrentThread  //当前线程结构体指针
		mov edi, pNewThread      //要切换线程指针

		mov[esi + IM_SWITCH_THREAD.KernelStack], esp  //保存当前堆栈栈顶

		//------------------线程切换------------------------------
		mov esp, [edi + IM_SWITCH_THREAD.KernelStack]

		pop eax
		pop edx
		pop ecx
		pop ebx
		pop esi
		pop edi
		pop ebp

		ret     //call startUp
	}
}

此处的 ret 会call 之前的地址。

PushStack(&StackDwordParam, (unsigned int)ThreadStartUp); //线程入口

因为根据入栈顺序在 mov esp, [edi + IM_SWITCH_THREAD.KernelStack] 之后栈的情况:

esp	->	eax
		edx
		ecx
		ebx
		esi
		edi
		ebp
		ThreadStartUp
		7
		lpParameter

经过多次出栈后,ret会直接call 到 ThreadStartUp 函数的位置。

线程入口函数

这是最简单也是最难理解的函数,其中当切换完堆栈后 ret 回跳到我们指定的入口函数的位置,注意,所有线程一开始的入口函数是一样的,通过在入口函数中根据每个线程的入口函数地址不同,调用不同函数。

void ThreadStartUp(PIM_SWITCH_THREAD pSwitchThread)
{
	pSwitchThread.iFlage = THREAD_CREAD;
	pSwitchThread->func(pSwitchThread->lpParameter);  \\调用函数地址
	
	ExecuteThread();
}

我们通过,ret指令直接调用的 ThreadStartUp 函数,也就是说没有向 ThreadStartUp 函数中, push 参数的这一步,那么我们在里面使用的线程结构体是从哪来的呢,当 ret 之后我们的线程堆栈是这样的:

    	eax
		edx
		ecx
		ebx
		esi
		edi
		ebp
		ThreadStartUp
esp	->  7
		lpParameter

来看下 ThreadStartUp 函数的反汇编:

void ThreadStartUp(PIM_SWITCH_THREAD pSwitchThread)
{
	00882A90  push        ebp  
	00882A91  mov         ebp,esp  
	00882A93  sub         esp,0C0h  
	00882A99  push        ebx  
	00882A9A  push        esi  
	00882A9B  push        edi  
	00882A9C  lea         edi,[ebp-0C0h]  
	00882AA2  mov         ecx,30h  
	00882AA7  mov         eax,0CCCCCCCCh  
	00882AAC  rep stos    dword ptr es:[edi]  
	00882AAE  mov         ecx,offset _3F265073_ThreadSwitch@cpp (089805Ch)  
	00882AB3  call        @__CheckForDebuggerJustMyCode@4 (08813ACh)  
		ImThreadList_My[iCurrentThreadIndex].iFlage = THREAD_CREAD;
	00882AB8  mov         eax,dword ptr [iCurrentThreadIndex (0894138h)]  
	00882ABD  shl         eax,5  
	00882AC0  mov         dword ptr [eax+88C13Ch],1  
		pSwitchThread->func(pSwitchThread->lpParameter);
	00882ACA  mov         esi,esp  
	00882ACC  mov         eax,dword ptr [ebp - 8]   \\有时候传结构体回传指针即 [pSwitchThread],需要另外改
	00882ACF  mov         ecx,dword ptr [eax+18h]  
	00882AD2  push        ecx  
	00882AD3  mov         edx,dword ptr [pSwitchThread]  
	00882AD6  mov         eax,dword ptr [edx+1Ch]  
	00882AD9  call        eax  
	00882ADB  add         esp,4  
	00882ADE  cmp         esi,esp  
	00882AE0  call        __RTC_CheckEsp (08812B2h)  
		
		ExecuteThread();
	00882AE5  call        ExecuteThread (08813DEh)  
}

根据:

00882ACC  mov         eax,dword ptr [ebp-8]  
00882ACF  mov         ecx,dword ptr [eax+18h]  

[ebp-8] 的位置刚好是我们 PIM_SWITCH_THREAD (线程结构体)的位置,刚进函数时 push 了 ebp, 所以此时堆栈为:

    	eax
		edx
		ecx
		ebx
		esi
		edi
		ebp
   esp->ThreadStartUp
        7
		lpParameter

所以 [ebp-8] 的位置刚好是我们 PIM_SWITCH_THREAD (线程结构体)的位置。添加的 7 主要是用来平衡堆栈的。

ThreadSwitch.h

#include <windows.h>

#define THREAD_MAX_COUNT 1024 //线程最大数量

#define STACK_SIZE 0x8000 //堆栈大小

#define THREAD_CREAD 1 //创建flage
#define THREAD_EXECUTE 2 //执行 


//线程结构体(_EHREAD)
typedef struct _IM_SWITCH_THREAD
{
	char* name;      //线程名
	int iFlage;      //线程状态
	int SleepTime;   //休眠时间

	void* InitialStack; //线程堆栈起始的位置
	void* StackLimit;   //线程堆栈界限
	void* KernelStack;  //线程堆栈当前的位置,即ESP

	void* lpParameter;  //线程函数的参数
	void (*func)(void* lpParameterFunction); //线程函数

}IM_SWITCH_THREAD, *PIM_SWITCH_THREAD;

//保存线程数组
static IM_SWITCH_THREAD ImThreadList_My[THREAD_MAX_COUNT] = { 0 };

static int iCurrentThreadIndex = 0;

int ImCreadThreadFunc(char* name, void (*func)(void* lpParameterFunction), void* lpParameter);

void initImThread(PIM_SWITCH_THREAD ImThreadInfo, char* name, void (*func)(void* lpParameterFunction), void* lpParameter);

void PushStack(unsigned int** iStackpp, unsigned int iVale);

void ExecuteThread();

//__declspec(naked) void SwitchThreadContext(PIM_SWITCH_THREAD pCurrentThread, PIM_SWITCH_THREAD pNewThread);

void ThreadStartUp(PIM_SWITCH_THREAD pSwitchThread);

ThreadSwitch.cpp

#include "ThreadSwitch.h"

void PushStack(unsigned int **iStackpp, unsigned int iVale)
{
	*iStackpp -= 1;  
	**iStackpp = iVale;
}

__declspec(naked) void SwitchThreadContext(PIM_SWITCH_THREAD pCurrentThread, PIM_SWITCH_THREAD pNewThread)
{
	_asm
	{
		push ebp
		mov ebp, esp  //提升堆栈

		push edi
		push esi
		push ebx
		push ecx
		push edx
		push eax

		mov esi, pCurrentThread  //当前线程结构体指针
		mov edi, pNewThread      //要切换线程指针

		mov[esi + IM_SWITCH_THREAD.KernelStack], esp  //保存当前堆栈栈顶

		//------------------线程切换------------------------------
		mov esp, [edi + IM_SWITCH_THREAD.KernelStack]

		pop eax
		pop edx
		pop ecx
		pop ebx
		pop esi
		pop edi
		pop ebp

		ret     //call startUp
	}
}

//初始化线程堆栈,创建线程
int ImCreadThreadFunc(char* name, void (*func)(void* lpParameterFunction), void* lpParameter)
{
	int i = 1;

	for (; ImThreadList_My[i].name; i++)
	{
		if (_stricmp(ImThreadList_My[i].name, name) == 0)
		{
			break;
		}
	}

	//初始化线程堆栈
	initImThread(&ImThreadList_My[i], name, func, lpParameter);

	return i;
}

//初始化堆栈
void initImThread(PIM_SWITCH_THREAD ImThreadInfo, char* name, void (*func)(void* lpParameterFunction), void* lpParameter)
{
	unsigned char* StackPages;
	unsigned int* StackDwordParam;

	//申请空间用来保存线程栈(申请物理页)
	StackPages = (unsigned char*)VirtualAlloc(NULL, STACK_SIZE, MEM_COMMIT, PAGE_READWRITE);

	//初始化内存
	memset(StackPages, 0, STACK_SIZE);

	//填充结构体
	ImThreadInfo->iFlage = THREAD_CREAD;
	ImThreadInfo->InitialStack = StackPages + STACK_SIZE;  
	ImThreadInfo->StackLimit = StackPages;
	ImThreadInfo->name = name;
	ImThreadInfo->func = func;
	ImThreadInfo->lpParameter = lpParameter;

	StackDwordParam = (unsigned int*)(StackPages + STACK_SIZE);

	//入栈
	PushStack(&StackDwordParam, (unsigned int)lpParameter); //压入函数参数
	PushStack(&StackDwordParam, (unsigned int)7);           //随便写一个参数,用来平衡堆栈
	PushStack(&StackDwordParam, (unsigned int)ThreadStartUp); //线程入口
	PushStack(&StackDwordParam, (unsigned int)0);     //push ebp
	PushStack(&StackDwordParam, (unsigned int)0);	  //push edi
	PushStack(&StackDwordParam, (unsigned int)0);	  //push esi
	PushStack(&StackDwordParam, (unsigned int)0);     //push ebx
	PushStack(&StackDwordParam, (unsigned int)0);     //push ecx
	PushStack(&StackDwordParam, (unsigned int)0);     //push edx
	PushStack(&StackDwordParam, (unsigned int)0);     //push eax

	ImThreadInfo->KernelStack = StackDwordParam;   //栈顶

	return;
}

//执行线程
void ExecuteThread()
{
	//GetTickCount
	int i = 1;

	PIM_SWITCH_THREAD pCurrentThread = NULL; //当前执行线程
	PIM_SWITCH_THREAD pNewThread = NULL; //当前执行线程

	pCurrentThread = &ImThreadList_My[iCurrentThreadIndex];

	for (; ImThreadList_My[i].name; i++)
	{
		if (ImThreadList_My[i].iFlage == THREAD_CREAD)
		{
			ImThreadList_My[i].iFlage = THREAD_EXECUTE;
			iCurrentThreadIndex = i;
			break;
		}
	}

	pNewThread = &ImThreadList_My[i];

	//切换线程
	SwitchThreadContext(pCurrentThread, pNewThread);

	return;
}



void ThreadStartUp(PIM_SWITCH_THREAD pSwitchThread)
{
	ImThreadList_My[iCurrentThreadIndex].iFlage = THREAD_CREAD;
	pSwitchThread->func(pSwitchThread->lpParameter);
	
	ExecuteThread();
}

main.cpp

#include <iostream>
#include <windows.h>
#include "ThreadSwitch.h"

using namespace std;


void Thread1(void* pVale)
{
	cout << "------------Thread1-----------------" << endl;
	ExecuteThread();

}

void Thread2(void* pVale)
{
	cout << "------------Thread2-----------------" << endl;
	ExecuteThread();
}

void Thread3(void* pVale)
{
	cout << "------------Thread3-----------------" << endl;
	ExecuteThread();
}


int main()
{
	const char* thread1 = "Thread1";
	const char* thread2 = "Thread2";
	const char* thread3 = "Thread3";

	ImCreadThreadFunc((char*)thread1, Thread1, NULL);
	ImCreadThreadFunc((char*)thread2, Thread2, NULL);
	ImCreadThreadFunc((char*)thread3, Thread3, NULL);

	ExecuteThread();

	/*
	while (TRUE)
	{
		Sleep(20);
		ExecuteThread();
	}
	*/
}

蓝色妖姬 – 相守