进程与线程-模拟线程切换

admin 2023年12月16日19:04:11评论15 views字数 11666阅读38分53秒阅读模式

调度链表

在这之前,要先补充几个知识。

在前面,我们通过内核驱动对进程进行断链隐藏,发现并不会影响进程的执行,那么这是不是说明CPU对进程的控制并不是通过这个链表呢?

答案是肯定的。

在操作系统中,共有33个链表,而线程有且只有3种状态,就绪、等待、运行。

正在运行中的线程存储在KPCR中,就绪和等待的线程全部在另外的33个链表中。一个等待链表,32个就绪链表(调度)。

而这些链表都使用了_KTHREAD(0x060)这个位置,也就是说,线程在某一时刻,只能属于其中一个圈。

等待链表

KiWaitListHead

比如:线程调用了Sleep() 或者 WaitForSingleObject()等函数时,就挂到这个链表

kd> dd KiWaitListHead
80563308 85d740c0 85d9cb88 00000011 00000000
80563318 e57a42bd d6bf94d5 01000013 ffdff980
80563328 ffdff980 804e2fc0 00000000 0000fc3c
80563338 00000000 ffdff9c0 80563340 80563340
80563348 00000000 00000000 80563350 80563350
80563358 00000000 00000000 00000000 863aeaf0
80563368 00000000 00000000 00040001 00000000
80563378 863aeb60 863aeb60 00000001 00000000


kd> dt _ETHREAD 85d740c0-60
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER 0x0ed17eac`f7206bd0
+0x1c0 NestedFaultCount : 0y00
+0x1c0 ApcNeeded : 0y0
+0x1c8 ExitTime : _LARGE_INTEGER 0x85d74228`85d74228
+0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x85d74228 - 0x85d74228 ]
+0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x85d74228 - 0x85d74228 ]
+0x1d0 ExitStatus : 0n0
+0x1d0 OfsChain : (null)
+0x1d4 PostBlockList : _LIST_ENTRY [ 0x85d74234 - 0x85d74234 ]
+0x1dc TerminationPort : 0xe1d31528 _TERMINATION_PORT
+0x1dc ReaperLink : 0xe1d31528 _ETHREAD
+0x1dc KeyedWaitValue : 0xe1d31528 Void
+0x1e0 ActiveTimerListLock : 0
+0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x85d74244 - 0x85d74244 ]
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : (null)
+0x208 LpcWaitingOnPort : (null)
+0x20c ImpersonationInfo : (null)
+0x210 IrpList : _LIST_ENTRY [ 0x85d74270 - 0x85d74270 ]
+0x218 TopLevelIrp : 0
+0x21c DeviceToVerify : (null)
+0x220 ThreadsProcess : 0x85d8bda0 _EPROCESS
+0x224 StartAddress : 0x7c8106e9 Void
+0x228 Win32StartAddress : 0x77e0e955 Void
+0x228 LpcReceivedMessageId : 0x77e0e955
+0x22c ThreadListEntry : _LIST_ENTRY [ 0x85d8bf30 - 0x85d868d4 ]
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : 0
+0x240 ReadClusterSize : 7
+0x244 GrantedAccess : 0x1f03ff
+0x248 CrossThreadFlags : 0
+0x248 Terminated : 0y0
+0x248 DeadThread : 0y0
+0x248 HideFromDebugger : 0y0
+0x248 ActiveImpersonationInfo : 0y0
+0x248 SystemThread : 0y0
+0x248 HardErrorsAreDisabled : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SkipCreationMsg : 0y0
+0x248 SkipTerminationMsg : 0y0
+0x24c SameThreadPassiveFlags : 0
+0x24c ActiveExWorker : 0y0
+0x24c ExWorkerCanWaitUser : 0y0
+0x24c MemoryMaker : 0y0
+0x250 SameThreadApcFlags : 0
+0x250 LpcReceivedMsgIdValid : 0y0
+0x250 LpcExitThreadCalled : 0y0
+0x250 AddressSpaceOwner : 0y0
+0x254 ForwardClusterOnly : 0 ''
+0x255 DisablePageFaultClustering : 0 ''
kd> dt _kTHREAD 85d740c0-60
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY [ 0x85d74070 - 0x85d74070 ]
+0x018 InitialStack : 0xf7373000 Void
+0x01c StackLimit : 0xf7370000 Void
+0x020 Teb : 0x7ffd5000 Void
+0x024 TlsArray : (null)
+0x028 KernelStack : 0xf7372960 Void
+0x02c DebugActive : 0 ''
+0x02d State : 0x5 ''
+0x02e Alerted : [2] ""
+0x030 Iopl : 0 ''
+0x031 NpxState : 0xa ''
+0x032 Saturation : 0 ''
+0x033 Priority : 13 ''
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : 0xff
+0x050 IdleSwapBlock : 0 ''
+0x051 Spare0 : [3] ""
+0x054 WaitStatus : 0n0
+0x058 WaitIrql : 0 ''
+0x059 WaitMode : 1 ''
+0x05a WaitNext : 0 ''
+0x05b WaitReason : 0x6 ''
+0x05c WaitBlockList : 0x85d740d0 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY [ 0x861f6080 - 0x86174aa0 ]
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : 0x3ae8e
+0x06c BasePriority : 13 ''
+0x06d DecrementCount : 0x10 ''
+0x06e PriorityDecrement : 0 ''
+0x06f Quantum : 4 ''
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : (null)
+0x0d4 KernelApcDisable : 0
+0x0d8 UserAffinity : 1
+0x0dc SystemAffinityActive : 0 ''
+0x0dd PowerState : 0 ''
+0x0de NpxIrql : 0 ''
+0x0df InitialNode : 0 ''
+0x0e0 ServiceTable : 0x80563520 Void
+0x0e4 Queue : (null)
+0x0e8 ApcQueueLock : 0
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x120 SoftAffinity : 1
+0x124 Affinity : 1
+0x128 Preempted : 0 ''
+0x129 ProcessReadyQueue : 0 ''
+0x12a KernelStackResident : 0x1 ''
+0x12b NextProcessor : 0 ''
+0x12c CallbackStack : (null)
+0x130 Win32Thread : (null)
+0x134 TrapFrame : 0xf7372d64 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] 0x85d74094 _KAPC_STATE
+0x140 PreviousMode : 1 ''
+0x141 EnableStackSwap : 0x1 ''
+0x142 LargeStack : 0 ''
+0x143 ResourceIndex : 0 ''
+0x144 KernelTime : 0
+0x148 UserTime : 0
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : 0 ''
+0x165 ApcStateIndex : 0 ''
+0x166 ApcQueueable : 0x1 ''
+0x167 AutoAlignment : 0 ''
+0x168 StackBase : 0xf7373000 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY [ 0x85d8bdf0 - 0x85d86858 ]
+0x1b8 FreezeCount : 0 ''
+0x1b9 SuspendCount : 0 ''
+0x1ba IdealProcessor : 0 ''
+0x1bb DisableBoost : 0 ''

调度链表

调度链表有32个圈,就是优先级:0 - 31  0最低  31最高  默认优先级一般是8

改变优先级就是从一个圈里面卸下来挂到另外一个圈上

这32个圈是正在调度中的线程:包括正在运行的和准备运行的

比如:只有一个CPU但有10个线程在运行,那么某一时刻,正在运行的线程在KPCR中,其他9个在这32个圈中。

XP只有一个33个圈,也就是说上面这个数组只有一个,多核也只有一个.

Win7也是一样的只有一个圈,如果是64位的,那就有64个圈.

既然有32个链表,就要有32个链表头。
kd> dd KiDispatcherReadyListHead L70
80563da0 80563da0 80563da0 80563da8 80563da8
80563db0 80563db0 80563db0 80563db8 80563db8
80563dc0 80563dc0 80563dc0 80563dc8 80563dc8
80563dd0 80563dd0 80563dd0 80563dd8 80563dd8
80563de0 80563de0 80563de0 80563de8 80563de8
80563df0 80563df0 80563df0 80563df8 80563df8
80563e00 80563e00 80563e00 80563e08 80563e08
80563e10 80563e10 80563e10 80563e18 80563e18
80563e20 80563e20 80563e20 80563e28 80563e28
80563e30 80563e30 80563e30 80563e38 80563e38
80563e40 80563e40 80563e40 80563e48 80563e48
80563e50 80563e50 80563e50 80563e58 80563e58
80563e60 80563e60 80563e60 80563e68 80563e68
80563e70 80563e70 80563e70 80563e78 80563e78
80563e80 80563e80 80563e80 80563e88 80563e88
80563e90 80563e90 80563e90 80563e98 80563e98
80563ea0 00000000 00000000 00000000 00000000
80563eb0 00000000 00000000 00000000 00000000
80563ec0 00000000 00000000 00000000 00000000
80563ed0 00000000 e1006000 00000000 00000000
80563ee0 00000001 eec66c90 00000000 00040001
80563ef0 00000000 80563ef4 80563ef4 00000000
80563f00 00000000 863e83d0 863e85a0 00000000
80563f10 00000000 00000000 80563f18 80563f18
80563f20 861d1278 861c5c68 00000000 00000000
80563f30 00040000 00000000 80563f38 80563f38
80563f40 c03de054 00000000 00034c00 00000000
80563f50 00000000 00000000 00000000 00000000

总结

1正在运行的线程在KPCR

2、准备运行的线程在32个调度链表中(0 - 31)KiDispatcherReadyListHead 是个数组存储了这32个链表头.

3、等待状态的线程存储在等待链表中,KiWaitListHead存储链表头.

4、这些圈都挂一个相同的位置:_KTHREAD(0x060)

模拟线程切换

  • ThreadSwitch.cpp

// ThreadSwitch.cpp: implementation of the ThreadSwitch class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <windows.h>
#include "ThreadSwitch.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////



//定义线程栈的大小
#define GMTHREADSTACKSIZE 0x80000

//当前线程的索引
int CurrentThreadIndex = 0;

//线程的列表
GMThread_t GMThreadList[MAXGMTHREAD] = {NULL, 0};

//线程状态的标志
enum FLAGS
{
GMTHREAD_CREATE = 0x1,
GMTHREAD_READY = 0x2,
GMTHREAD_SLEEP = 0x4,
GMTHREAD_EXIT = 0x8,
};

//启动线程的函数
void GMThreadStartup(GMThread_t* GMThreadp)
{
GMThreadp->func(GMThreadp->lpParameter);//执行线程的函数
GMThreadp->Flags = GMTHREAD_EXIT;//设置线程的状态为exit
Scheduling();//让出线程

return;
}

//空闲线程的函数
void IdleGMThread(void* lpParameter)
{
printf("IdleGMThread---------------n");
Scheduling();
return;
}

//向栈中压入一个uint值
void PushStack(unsigned int** Stackpp, unsigned int v)
{
*Stackpp -= 1;//地址减4
**Stackpp = v;//给改地址赋值

return;
}

//初始化线程的信息
void initGMThread(GMThread_t* GMThreadp, char* name, void (*func)(void* lpParameter), void* lpParameter)
{
unsigned char* StackPages;
unsigned int* StackDWordParam;
GMThreadp->Flags = GMTHREAD_CREATE;//设置flag为create
GMThreadp->name = name;//设置线程名字
GMThreadp->func = func;//设置线程需要执行的函数地址
GMThreadp->lpParameter = lpParameter;//设置线程的参数地址
StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);//分配内存(地址系统自动分配,大小0x8000,分配类型地址空间和物理页都分,访问类型)
ZeroMemory(StackPages, GMTHREADSTACKSIZE);//初始化分配的内存都为0
GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;//设置线程的堆栈的起始位置,就说分配的内存的最大地址
StackDWordParam = (unsigned int*)GMThreadp->initialStack;
//入栈
PushStack(&StackDWordParam, (unsigned int)GMThreadp);//startup 函数所需要的参数
PushStack(&StackDWordParam, (unsigned int)0);//你好奇这里为什么放0,简单来说是为了平衡堆栈,其次是因为调用startup是要参数的,
//pop startup->eip后 esp也就是这里,进函数后会把mov ebp,esp 然后ebp+8 就是函数默认的参数位置,这也就是这里为什么多push一个四字节。
PushStack(&StackDWordParam, (unsigned int)GMThreadStartup);
PushStack(&StackDWordParam, (unsigned int)5);
PushStack(&StackDWordParam, (unsigned int)7);
PushStack(&StackDWordParam, (unsigned int)6);
PushStack(&StackDWordParam, (unsigned int)3);
PushStack(&StackDWordParam, (unsigned int)2);
PushStack(&StackDWordParam, (unsigned int)1);
PushStack(&StackDWordParam, (unsigned int)0);
//当前线程的栈顶
GMThreadp->KernelStack = StackDWordParam;//线程当前的ESP的位置
GMThreadp->Flags = GMTHREAD_READY;//设置flag为ready
return;
}

//将一个函数注册为单独线程执行
int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter)//线程名字,函数指针,函数参数指针
{
int i;
for (i = 1; GMThreadList[i].name; i++) {//遍历线程列表,如果遍历完没有,就会获取到一个新的列表下表
if (0 == _stricmp(GMThreadList[i].name, name)) {//比较线程名字,如果名字相同,说明该线程已经注册
break;
}
}
initGMThread(&GMThreadList[i], name, func, lpParameter);//初始化线程 (初始化线程列表地址,线程名字,函数指针,函数参数指针)
return (i & 0x55AA0000);
}

//切换线程
__declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp)
{
__asm {
push ebp
mov ebp, esp //esp == ebp
push edi
push esi
push ebx
push ecx
push edx
push eax //上面都是保留当前线程的现场

mov esi, SrcGMThreadp
mov edi, DstGMThreadp
mov [esi+GMThread_t.KernelStack], esp //将当前的esp的,赋给当前线程结构体的KernelStack
//经典线程切换,另外一个线程复活
mov esp, [edi+GMThread_t.KernelStack] //将目标线程的内核堆栈的地址给到esp

pop eax //esp在上面已经切换到新的线程栈中,这个栈再pop eax,拿到的就是保存的esp(初始化的esp/运行时esp)
pop edx
pop ecx
pop ebx
pop esi
pop edi
pop ebp
ret //把栈顶的值弹到eip中,在这里弹出的就是startup的地址到eip中
}
}

//这个函数会让出cpu,从队列里重新选择一个线程执行
void Scheduling(void)
{
int i;
int TickCount;
GMThread_t* SrcGMThreadp;
GMThread_t* DstGMThreadp;
TickCount = GetTickCount();//它返回从操作系统启动到当前所经过的毫秒数
SrcGMThreadp = &GMThreadList[CurrentThreadIndex];//当前线程的线程列表的地址
DstGMThreadp = &GMThreadList[0];//线程列表的首地址

for (i = 1; GMThreadList[i].name; i++) {//开始遍历,结束条件就是当前有的线程
if (GMThreadList[i].Flags & GMTHREAD_SLEEP) {//如果是SLEEP线程
if (TickCount > GMThreadList[i].SleepMillsecondDot) {//判断时间睡够没有
GMThreadList[i].Flags = GMTHREAD_READY;//如果睡够了,就把FLAG置为ready
}
}
if (GMThreadList[i].Flags & GMTHREAD_READY) {//如果是准备好的线程
DstGMThreadp = &GMThreadList[i];//获取目标线程的首地址
break;//跳出循环
}
}

CurrentThreadIndex = DstGMThreadp - GMThreadList;//当前线程索引 = 目标线程首地址 - 线程链表的首地址 (不明白的去补一下基础哦)
SwitchContext(SrcGMThreadp, DstGMThreadp);//线程切换(当前的线程,目标线程)
return;
}

void GMSleep(int MilliSeconds)
{
GMThread_t* GMThreadp;
GMThreadp = &GMThreadList[CurrentThreadIndex];//当前线程
if (GMThreadp->Flags != 0) {//容错,不可能是0哈
GMThreadp->Flags = GMTHREAD_SLEEP;//设置线程状态为sleep
GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;//唤醒时间 = 设置的睡眠时间 + 从操作系统启动到当前所经过的毫秒数
}

Scheduling();//可以让出CPU了
return;
}
  • ThreadSwitch.h

// ThreadSwitch.h: interface for the ThreadSwitch class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_THREADSWITCH_H__64EDD393_3CEB_462D_8499_A3C2006C37E0__INCLUDED_)
#define AFX_THREADSWITCH_H__64EDD393_3CEB_462D_8499_A3C2006C37E0__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <windows.h>
#include "stdio.h"
#pragma once
//最大支持的线程数
#define MAXGMTHREAD 100

//线程信息的结构
typedef struct
{

char* name; //线程名 相当于线程ID
int Flags; //线程状态
int SleepMillsecondDot; //休眠时间

void* initialStack; //线程堆栈起始位置
void* StackLimit; //线程堆栈界限
void* KernelStack; //线程堆栈当前位置,也就是ESP

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

void GMSleep(int MilliSeconds);
int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter);
void Scheduling();


#endif // !defined(AFX_THREADSWITCH_H__64EDD393_3CEB_462D_8499_A3C2006C37E0__INCLUDED_)
  • 模拟现场切换.cpp

// 模拟现场切换.cpp : Defines the entry point for the console application.
//

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

extern int CurrentThreadIndex;
extern GMThread_t GMThreadList[MAXGMTHREAD];

void Thread1(void*) {
while(1){
printf("Thread1n");
GMSleep(500);
}
}
void Thread2(void*) {
while (1) {
printf("Thread2n");
GMSleep(500);
}
}
void Thread3(void*) {
while (1) {
printf("Thread3n");
GMSleep(500);
}
}
void Thread4(void*) {
while (1) {
printf("Thread4n");
GMSleep(500);
}
}
int main()
{
RegisterGMThread("Thread1", Thread1, NULL);
RegisterGMThread("Thread2", Thread2, NULL);
RegisterGMThread("Thread3", Thread3, NULL);
RegisterGMThread("Thread4", Thread4, NULL);

while(TRUE) {
Sleep(20);
Scheduling();
}
return 0;
}


原文始发于微信公众号(loochSec):进程与线程-模拟线程切换

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月16日19:04:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   进程与线程-模拟线程切换http://cn-sec.com/archives/2308536.html

发表评论

匿名网友 填写信息