进程线程005 SwapContext函数分析
文章目录
- 线程切换与TSS
- 内核堆栈
- 内核堆栈结构
- 调用API进零环
- SwapContext代码分析
- 线程切换与FS
- SwapContext代码分析
- SwapContext的其他问题
- SwapContext有几个参数 怎么判断出来的?
- SwapContext在哪里实现了线程切换?
- 0环的ExceptionList是在哪里备份的
线程切换与TSS
SwapContext这个函数是Windows线程切换的核心,无论是主动切换还是系统时钟导致的线程切换,最终都会调用这个函数
在这个函数除了切换堆栈以外,还做了很多其他事情,下面就来学习一下线程切换与TSS的关系
内核堆栈
每一个线程都有一个内核堆栈,当API调用进零环的时候,必然要切换堆栈这个堆栈就是当前线程的零环堆栈。那这个线程零环的堆栈去哪里找呢?
KTHREAD结构体中有三个成员:InitialStack是当前堆栈的栈底,KernelStack是当前堆栈的栈顶,StackLimit是堆栈的边界。也就是说如果我们找到了这三个成员也就找到了内核堆栈。
内核堆栈结构
内核堆栈从结构上大体分成两部分,第一部分从InitialStack开始的0x210个字节存储的是当前线程用到的浮点寄存器的值。
从0x210再往后就是Trap_Frame结构体。完整结构如图:
调用API进零环
普通调用:通过TSS.ESP0得到零环堆栈
快速调用:从MSR得到一个临时的0环栈,代码执行后仍然通过TSS.ESP0得到当前线程的0环栈
我们找到KiFastCallEntry的代码,0FFDFF000的位置是KPCR,首先找到KPCR偏移为0x40的位置TSS,然后再找到TSS偏移4的位置ESP0,把这个值赋给了当前的esp。然后才开始往堆栈里压入值。
那么问题来了,**TSS中的ESP0来自于哪?**答案在SwapContext的代码里。
SwapContext代码分析
Intel设计TSS的目的是为了任务切换(线程切换),但Windows与Linux并没有使用,而是采用堆栈来保存线程的各种寄存器。
那么这里就有一个问题,**一个CPU只有一个TSS,但是线程很多,如何用一个TSS来保存所有线程的ESP0呢?**答案都在SwapContext的代码里
找到SwapContext中与TSS相关的代码,ebx就是当前CPU对应的结构体KPCR,通过KPCR找到TSS存到ecx里,
TSS偏移4的位置是ESP0,接着将eax存储到ESP0,继续往上找一下eax的值。
首先将目标线程的栈底存储到eax
此时eax指向上图的的InitStack
接着减去0x210
此时eax指向Trap_Frame结构
接着再减去0x10,也就是4个成员
Trap_Frame最底下的四个成员是给虚拟8086模式用的。通过刚才的计算得出,当前的eax指向的是0x078的位置SS
.text:00469B1C mov ecx, [ebx+40h] ; 通过KPCR取出TSS
.text:00469B1F mov [ecx+4], eax ; 将Trap_Frame.ESP0存到TSS.ESP0
这就是SwapContext函数对TSS的使用,它会将Trap_Frame.ESP0存到TSS.ESP0。
到这里,解决了之前提出的两个问题
**TSS中的ESP0来自于哪?**来自于0环的Tram_Frame结构体
**一个CPU只有一个TSS,但是线程很多,如何用一个TSS来保存所有线程的ESP0呢?**在发生线程切换的时候,SwapContext会将目标线程的ESP0存到TSS中,然后开始切换线程。就是说TSS永远存储的是当前线程的ESP0
TSS中除了ESP0之外还用到了一个值就是CR3,SwapContext会将当前TSS中的CR3修改为目标进程的CR3,然后切换CR3。
下面一行代码将当前线程的IO权限位图存到了TSS里。这个成员在Windows2000以后不用了
**结论:**Intel提供的TSS在Windows里只有三个成员是有意义的:ESP0 CR3和IO权限位图
线程切换与FS
FS:[0]寄存器在3环的时候指向TEB,进入0环后FS:[0]指向KPCR。
系统中同时存在很多个线程,这就意味着FS:[0]在3环的时候指向的TEB要有多个,有一个线程就要有一个TEB,但是在实际使用中我们发现在3环查看不同线程的FS寄存器时,FS的段选择子都是相同的,那么是如何实现通过一个FS寄存器指向多个TEB呢?
答案依然在SwapContext函数的代码里。
SwapContext代码分析
找到SwapContext与TEB相关的代码
首先取出目标线程的TEB 放到eax里
.text:00469B67 mov ecx, [ebx+3Ch] ; 通过KPCR找到GDT表
接着通过KPCR找到GDT表,存到ecx
.text:00469B6A mov [ecx+3Ah], ax
这里的ax是TEB的低16位,
而ecx+3A就是段描述符低4个字节的16-31位Base Address,也就是将TEB的低16位写到段描述符的16-31位。
.text:00469B6E shr eax, 10h
将eax右移16位,这样就能得到TEB地址的高16位。因为低16位已经写到段描述符里了。
.text:00469B71 mov [ecx+3Ch], al ; 将低8位写到段描述符0-7的位置
.text:00469B74 mov [ecx+3Fh], ah ; 将高8位写到段描述符31-24的位置
高16位又分成两部分写到段描述符中,先将低8位写到段描述符0-7的位置,再将高8位写到段描述符31-24的位置。
通过这几行代码就将新的线程的TEB的地址写到了当前GDT表的段描述符的基址中。
这就回答了刚才的问题:如何实现通过一个FS寄存器指向多个TEB?
因为FS段选择子虽然没有发生变化,但是在线程切换的时候,会修改段选择子所指向的段描述符的基址为新的线程的TEB的地址。
SwapContext的其他问题
SwapContext有几个参数 怎么判断出来的?
四个参数,但真正有用的只有三个,分别是:
- esi:当前线程结构体ETHREAD指针
- edi:要切换的线程结构体ETHREAD指针
- ebx:KPCR
首先找到SwapContext的父函数KiSwapContext
0FFDFF01Ch存储的就是KPCR,所以参数ebx就是KPCR
.text:004699CE mov edi, [ebx+124h] ; KPCR+0x124是当前线程的KTHREAD结构体
ebx是KPCR,KPCR+0x124的位置就是当前线程的KTHREAD结构体
.text:004699CC mov esi, ecx ; ecx是上一层调用的函数传进来的 是要切换线程的KTHREAD
而esi来自于ecx,ecx是上一层调用的函数传进来的 是要切换线程的KTHREAD
找到上一层的函数,ecx来自于eax,是KiFindReadyThread的返回值,这个函数会查找一个就绪线程返回KTHREAD结构体
SwapContext在哪里实现了线程切换?
线程切换的本质就是切换堆栈
这行代码将目标线程的KernelStack存到ESP里,这行代码以后另一个线程复活
0环的ExceptionList是在哪里备份的
这行代码会将ExceptionList存储到ecx,然后将ExceptionList保存到堆栈
进程线程005 SwapContext函数分析相关推荐
- 【操作系统/OS笔记10】进程/线程的调度原则、调度算法、实时调度、多处理器调度、优先级反转
本次笔记内容: 8.1 背景 8.2 调度原则 8.3 调度算法1 8.4 调度算法2 8.5 实时调度 8.6 多处理调度与优先级反转 文章目录 CPU调度背景 上下文切换 CPU调度 在进程/线程 ...
- Python之路 34:并发与并行、锁(GIL、同步锁、死锁与递归锁)、信号量、线程队列、生消模型、进程(基础使用、进程通信、进程池、回调函数)、协程
内容: 同步锁 死锁.递归锁 信号量和同步对象(暂时了解即可) 队列------生产者和消费者模型 进程(基础使用.进程通信.进程池.回调函数) 协程 一.并发并行与同步异步的概念 1.1.并发和并行 ...
- 浏览器层面优化前端性能(1):Chrom组件与进程/线程模型分析
现阶段的浏览器运行在一个单用户,多合作,多任务的操作系统中.一个糟糕的网页同样可以让一个现代的浏览器崩溃.其原因可能是一个插件出现bug,最终的结果是整个浏览器以及其他正在运行的标签被销毁. 现代操作 ...
- 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 注入工具的 main 函数分析 )
文章目录 一.注入流程 二.注入工具的 main 函数分析 一.注入流程 开始分析 [Android 逆向]Android 进程注入工具开发 ( 编译注入工具 | 编译结果文件说明 | 注入过程说明 ...
- windows内核情景分析---进程线程2
二.线程调度与切换 众所周知:Windows系统是一个分时抢占式系统,分时指每个线程分配时间片,抢占指时间片到期前,中途可以被其他更高优先级的线程强制抢占. 背景知识:每个cpu都有一个TSS,叫'任 ...
- windows内核情景分析---进程线程1
本篇主要讲述进程的启动过程.线程的调度与切换.进程挂靠 一.进程的启动过程: BOOL CreateProcess ( LPCTSTR lpApplicationName, ...
- Linux系统编程----15(线程与进程函数之间的对比,线程属性及其函数,线程属性控制流程,线程使用注意事项,线程库)
对比 进程 线程 fork pthread_create exit (10) pthread_exit (void *) wait (int *) pthread_join (,void **)阻塞 ...
- 0820Python总结-线程队列,进程池和线程池,回调函数,协程
一.线程队列 from queue import Queue put 存 get 取 put_nowait 存,超出了队列长度,报错 get_nowait 取,没数据时,直接报错 Linux Wind ...
- 进程间的数据共享、进程池的回调函数和线程初识、守护线程
一.进程的数据共享 进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的 虽然进程间数据独立,但可以通过Manager实现数据共享.把所有实现了数据共享的比较便捷的类都重新又封装了 ...
最新文章
- C# 虚函数和重载函数
- python自动化测试locksetting/gatekeeper/keymaster/vts等
- BUUCTF(pwn)mrctf2020_easyoverflow
- zw版【转发·台湾nvp系列Delphi例程】HALCON DispCross
- Windows mysql boost_Win7下Boost库的安装
- 联想即将推出预装 Fedora 的 ThinkPad 笔记本电脑
- 剑指offer:数据流中的中位数(小顶堆+大顶堆)
- 英伟达推出新款“煤气灶”Titan RTX,售价近2万,并开源PhysX SDK
- 在centos7中安装nodejs(npm )
- Deepin安装Eclipse
- 非极大值抑制(Non-Maximum Suppression)
- RANSAC介绍(Matlab版直线拟合+平面拟合)
- Python 办公自动化之全网最强最详细PDF 文件操作手册
- 深入理解Instrument
- 泰拉瑞亚服务器config修改,《泰拉瑞亚》游戏配置怎么修改 游戏配置修改办法推荐...
- 【Django下载文件-Kml文件下载】
- 一、注册功能怎么测试
- 美光科技股价上涨13% 创下自2011年12月以来最大单日涨幅
- 如何查看小方侦测云存储_小方智能摄像机和手机怎样连接?
- 全栈之路-前端篇 | 第二讲.基础前置知识【应用服务端与编程语言】学习笔记
热门文章
- C++:C++语言入门级基础知识考察点回顾之基本数据类型、流程控制
- CSDN:因博主近期注重写专栏文章(已超过150篇),订阅博主专栏人数在突增,近期很有可能提高专栏价格(已订阅的不受影响),提前声明,敬请理解!
- DL之RNN:人工智能为你写代码——基于TF利用RNN算法实现生成编程语言代码(C++语言)、训练测试过程全记录
- CV之IC: 图像描述(Image Captioning) 的简介、使用方法、案例应用之详细攻略
- Hyperopt中文文档:Cite引用
- [Swift]LeetCode468. 验证IP地址 | Validate IP Address
- js格式化文件大小, 输出成带单位的字符串工具
- 使用dtd--属性声明
- Nrf51822中设置128bit UUID service
- UIView 学习知识点