目录
前言
调用API触发线程切换机制
KiSwapThread
参数分析
函数功能
KiSwapContext
SwapContext
前言
- 通过研究KiSwapThread函数,就可以知道单核CPU是如何同时运行多个进程.
- 需要了解进程内核结构KPROCESS,其成员CR3,ApcState等等.
- 需要了解线程内核结构KTHREAD,会用到很多线程内核结构成员.并且对WindowsAPI调用有了解(系统调用),需要了解一个API是如何进R0,以及如何从R3堆栈切换到R0堆栈等.
- 需要了解处理器控制区KPCR,线程切换会通过其成员ReadySummary / DisPatcherReadyListHead来查找当前核就绪线程等.
- 建议阅读此文前先查看模拟Windows线程切换,Windows查找就绪线程.
调用API触发线程切换机制
IDA查看函数KiSwapThread.
通过交叉引用查看哪些函数调用了线程切换函数.
通过交叉引用查看哪些函数调用了KiCommitThreadWait
通过KeWaitForSingleObject函数交叉引用可以看出,大部分API调用时最终会触发线程切换.
分析可以得出结论:Windows中绝大部分API最终都会调用了KiSwapThread(SwapContext后文详解)函数,也就意味着调用API相当于主动切换线程.
KiSwapThread
参数分析
通过交叉引用查看调用函数KiCommitThreadWait传递了哪些参数.
使用ECX,DEX传参可以得出调用约定为FASTCALL.
参数EDX来源于EBX,EBX函数上方赋值后指向_KPRCB
参数ECX来源于EDI,EDI为当前函数参数1(KTHREAD)
参考WRK定义
LONG_PTR
FASTCALL
KiSwapThread (
IN PKTHREAD OldThread, //当前线程
IN PKPRCB CurrentPrcb //当前核KPRCB
);
函数功能
线程切换KiSwapThread函数在不同版本系统下实现都有略微差异.只研究核心代码.
发现其实现中调用了KiSearchForNewThread函数.
分析得知函数首先直接获取_KPRCB结构中NextThread,如果此值不为空,将NextThread设置为0,并且把取出线程设置为KPRCB.CurrentThread,线程KTHREAD.State设置为2后直接返回该线程结构.
如果_KPRCB结构中NextThread为空,通过KiSelectReadyThread函数继续查找,该函数实现为直接查找指定优先级的线程,如果找到线程设置对应状态后返回.
如果KiSelectReadyThread函数也没找到,会执行KiFindReadyThread函数,通过KPRCB -> ReadySummary就绪位图循环查找符合执行条件的线程设置对应状态后返回.
KiSearchForNewThread函数首先判断是否找到就绪线程,找到跳转(这里按照成功路线分析).
取出线程不是IdleThread且不为当前线程,同时处于未运行状态会跳转.
线程查询校验全部完毕后最终跳转此处(EDX为要切换线程,ECX为当前线程),执行函数KiSwapContext.
KiSwapContext
函数并未做任何功能实现,而是初始化参数后调用SwapContext函数
SwapContext
函数入口会循环(自旋)判断要执行线程运行状态,直到未运行时跳出.
线程状态检测跳转后,会进行时间与浮点相关初始化,并且会将当前线程异常处理链表保存到堆栈中.
初始化工作结束后,开始切换堆栈(相当于切换线程(模拟线程中有详细介绍)),并且判断当前线程所属进程来决定是否需要切换CR3.至此实际上已经完成线程切换.
两个线程对应堆栈图如下:
系统调用分析中说过TSS.ESP0永远存储当前线程_KTRAP_FRAME,论证如下
内核堆栈结构以及通过_KTHREAD -> 堆栈相关回溯ESP0,获得_TRAP_FRAME:
段寄存器相关文章中提起,x86中未使用GS段寄存器,线程切换就会清空GS值,论证如下:
在介绍段寄存器说FS寄存器在R3时指向当前线程TEB,论证如下:
线程主动切换分析完毕,按照大致流程以及对段,TSS,_KTRAP_FRAME补充.这里再次说明,参考WRK, Windows内核原理与实现, Windows内核情景分析时不要完全按照它们的内容进行比较,对自己内核文件进行分析最为准确.同时要搞懂全部实现比较难,按照主线路分析即可.
当前内核文件MSDN搜索cn_windows_7_professional_with_sp1_x86_dvd_u_677162.iso