线程是不能被“杀掉”、“挂起”、“恢复”的,线程在执行的时候自己占据着CPU,别人怎么可能控制它呢?
举个极端的例子:如果不调用API,屏蔽中断,并保证代码不出现异常,线程将永久占用CPU,何谈控制呢?所以说线程如果想“死",一定是自己执行代码把自己杀死,不存在“他杀”这种情况!
那如果想改变一个线程的行为该怎么办呢?
可以给他提供一个函数,让它自己去调用,这个函数就是APC (Asyncroneus Procedure Call),即异步过程调用。
APC队列
kd> dt KTHREAD
nt! KTHREAD
...
+0x034 ApcState _KAPC_STATE
...
kd> dt _kapc_state
nt!_KAPC_STATE
+0x000 ApcListHead //2个APC队列用户APC和内核APC
+0x010 Process //线程所属或者所挂靠的进程_KPROCESS
+0x014 KernelApcInProgress //内核APC是否正在执行
+0x015 KernelApcPending //是否有正在等待执行的内核APC 1=有
+0x016 UserApcPending //是否有正在等待执行的用户APC 1=有
用户APC: APC函数地址位于用户空间,在用户空间执行
内核APC: APC函数地址位于内核空间,在内核空间执行
kd> dt _kapc
nt!_KAPC
+0x000 Type //类型APC类型为0x12
+0x002 Size //本结构体的大小0x30
+0x004 Spare0 //未使用
+0x008 Thread //目标线程
+0x00c ApcListEntry //APC队列挂的位置
+0x014 KernelRoutine //指向一个函数(调用ExFreePoolWithTag释放APC)
+0x018 RundownRoutine //未使用
+0x01c NormalRoutine //用户APC总入口或者真正的内核apc函数
+0x020 NormalContext //内核APC: NULL用户APC:真正的APC函数
+0x024 SystemArgument1 //APC函数的参数
+0x028 SystemArgument2 //APC函数的参数
+0x02c ApcStateIndex //挂哪个队列,有四个值: 0 1 2 3
+0x02d ApcMode //内核APC用户APC
+0x02e Inserted //表示本apc是否已挂入队列挂入前: 0挂入后1
+0x01c NormalRoutine 可以找到你提供APC函数在哪,并不完全等于APC函数的地址
如果我想改变一个线程:
- 提供一个APC
- 提供+0x01c执行要执行的函数在哪
- 把这个APC存到KTHREAD.+0x34 ApcState.ApcListHead
当前的线程什么时候会执行我们提供的这些APC函数?
判断是否有APC要执行
如果有APC
处理apc函数KiDeliverApc,这里我门发现只有用户的apc,其实内核的apc是一定会处理的它会先处理内核apc在处理用户的apc。
KiServiceExit函数:
这个函数是系统调用、异常或中断返回用户空间的必经之路。
KiDeliverApc函数:
负责执行APC函数