线程挂起和恢复
date
Sep 25, 2023
slug
suspend-and-resume-thread
status
Published
tags
Windows Internals
Thread
summary
这篇内容讲述了在Windows操作系统中挂起和恢复线程的过程。挂起线程通过插入一个APC(异步过程调用)来阻止线程执行,然后等待SuspendCount变为0才会继续执行。恢复线程则是通过发送信号量和调用相关函数来唤醒线程,最终等待CPU调度。整个过程涉及到多个内核函数和数据结构的操作,以实现线程的挂起和恢复。
type
Post
SuspendThread 挂起线程
调用链
SuspendThread
→ KERNEL32!SuspendThreadStub
→ KERNELBASE!SuspendThread
→ ntdll!NtSuspendThread
→ PsSuspendThread
→ KeSuspendThread
→ KiSuspendThread
→ KiInsertQueueApc
过程分析
未挂起前:
- 查看线程的 apc 列表:(空的,表示目前没有 apc)
- 查看线程的结构,看看 SuspendCount,通过
dt _KTHREAD ffffd3071fedd080
通过
SuspendThread
挂起线程后,再查看一下状态:- 线程的 apc 列表,增加了一个
ffffd3071fedd308
的 APC 对象
- KTHREAD 中的 SuspendCount 也增加了
- 再详细的看下增加的 APC 中包含什么内容
基本发现,就是塞进去了一个
nt!KiSchedulerApc
的方法。猜测这个方法是一直卡住,执行不完的。(Win2003中KiSuspendThread
逻辑就是等待 SuspendCount 变成0,KiSchedulerApc
中应该也是类似的逻辑)KiSchedulerApc
是在 Win10 22H2 上看到的,在 KiInsertQueueApc
中通过 Thread→SchedulerApc
塞进去的KiSchedulerApc
(结合UAC的 token 校验不过,直接终止,猜测其中包含 token 的判断逻辑);查看 Win2003 的代码(reactos也是)是通过 Thread→SuspendApc
,塞进去的 KiSuspendThread
。代码如下:总结
可以看到其实挂起线程相对比较简单,只是插入了一个 APC,让此线程一直无法执行而已。
关于 APC 可以看这里:
ResumeThread 恢复线程
调用链
ResumeThread
→ KERNEL32!ResumeThreadStub
→ KERNELBASE!ResumeThread
→ ntdll!NtResumeThread
→ PsResumeThread
→ KeResumeThread
→ KiWaitTest
→ KiUnwaitThread
→ KiReadyThread
→ KiDeferredReadyThread
详细过程
KeResumeThread
代码如下:KeResumeThread
核心做了几件事:- 根据 SuspendCount 和 FreezeCount 来判断是否需要恢复线程
- 通过发送信号量,调用
KiWaitTest
来恢复线程
KiWaitTest
的代码如下:KiWaitTest
主要做了以下事情:- 通过等待对象找到对象的等待队列
- 将等待队列中的每一个线程通过
KiUnwaitThread
来进行唤醒
- 其中
KiSatisfyObjectWait
就是通过设置信号状态,唤醒等待线程、切换上下文来响应等待的线程(这里详述看
KiUnwaitThread
的代码如下:KiUnwaitThread
主要分两块:- KiUnlinkThread:主要将线程从它正在等待的内核对象队列中移除,即不等待任何对象
- KiReadyThread:主要根据线程的状态(优先级、是否被分页了等),将线程置于 ready 状态,待 CPU 调度
总结
所谓的恢复线程,就是将线程的各种状态修改成 Ready 状态,然后告诉系统,我可以被调度了,然后等待 CPU 调度。这里的核心在于线程的调度。