聊一聊 C# 线程切换后上下文都去了哪里

一:背景

1. 讲故事

总会有一些朋友问一个问题,在 Windows 中线程做了上下文切换,请问被切的线程他的寄存器上下文都去了哪里?能不能给我挖出来?这个问题其实比较底层,如果对操作系统没有个体系层面的理解以及做过源码分析,其实很难说明白,这篇我们就从.NET高级调试的角度试着分析一下吧。

二:寄存器上下文去哪了

1. 用户线程的两态空间

用C#代码创建的线程在操作系统层面上来说属于 用户态线程,这种线程拥有两个线程栈,哈哈,是不是打破了一些朋友的三观。分别为 用户态栈内核态栈

为了方便讲解,写一段简单的测试代码,不断的调用 Sleep(1) 让代码在用户态和内核态不断的切换,也就能观察得到这两套栈空间,参考代码如下:

static void Main(string[] args){for (int i = 0; i < int.MaxValue; i++){Thread.Sleep(1);Console.WriteLine($"i={i}");}}

将程序跑起来后我们用 windbg 附加,观察这个程序的上下文,参考如下:


0: kd> !process 0 2 ConsoleApp7.exe
PROCESS ffffe00185e33440SessionId: 2  Cid: 0f4c    Peb: 7ff73b7a8000  ParentCid: 15f4DirBase: 1573c1000  ObjectTable: ffffc00165357840  HandleCount: <Data Not Accessible>Image: ConsoleApp7.exeTHREAD ffffe0018917a080  Cid 0f4c.0f50  Teb: 00007ff73b7ae000 Win32Thread: ffffe00185e3db20 WAIT: (DelayExecution) UserMode Alertableffffffffffffffff  NotificationEvent
...2: kd> dt nt!_KTHREAD ffffe0018917a080+0x028 InitialStack     : 0xffffd001`f8b64c90 Void+0x030 StackLimit       : 0xffffd001`f8b5f000 Void+0x038 StackBase        : 0xffffd001`f8b65000 Void...+0x058 KernelStack      : 0xffffd001`f8b63c80 Void...+0x0f0 Teb              : 0x00007ff7`3b7ae000 Void...2: kd> dt ntdll!_NT_TIB 0x00007ff7`3b7ae000+0x000 ExceptionList    : (null) +0x008 StackBase        : 0x00000035`35790000 Void+0x010 StackLimit       : 0x00000035`3577e000 Void+0x018 SubSystemTib     : (null) +0x020 FiberData        : 0x00000000`00001e00 Void+0x020 Version          : 0x1e00+0x028 ArbitraryUserPointer : (null) +0x030 Self             : 0x00007ff7`3b7ae000 _NT_TIB...

上面的信息非常清晰,两套栈空间 StackBase ~ StackLimit,分别为 0x0000003535790000 ~ 0x000000353577e0000xffffd001f8b5f000~0xffffd001f8b65000

2. 理解系统调用

理解了线程的两套栈空间之后,接下来说的就是系统调用,简单来说就是C#线程从 用户态 进入到 内核态 时,他的用户态寄存器上下文会存放到 _KTRAP_FRAME 结构体中,而这个结构体会放在内核态的线程栈上,有些朋友可能有点懵,画个图如下:

接下来的问题是如何验证呢?非常简单,第一种是通过 !thread 观察线程栈上的 TrapFrame 标记,第二种是提取内核线程的 _KTHREAD.TrapFrame 字段,为了方便测试,直接在 Sleep 的内核函数 NtDelayExecution 处下一个进程级别的断点,输出如下:


1: kd> bp /p ffffe00185e33440  nt!NtDelayExecution
breakpoint 0 redefined
1: kd> g
Breakpoint 0 hit
nt!NtDelayExecution:
fffff802`e4e8dfb0 4883ec28        sub     rsp,28h3: kd> !thread ffffe0018917a080
THREAD ffffe0018917a080  Cid 0f4c.0f50  Teb: 00007ff73b7ae000 Win32Thread: ffffe00185e3db20 RUNNING on processor 3
IRP List:ffffe00187633ca0: (0006,0358) Flags: 00060800  Mdl: 00000000
Not impersonating
DeviceMap                 ffffc0015d587160
Owning Process            ffffe00185e33440       Image:         ConsoleApp7.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      21032          Ticks: 1 (0:00:00:00.015)
Context Switch Count      8187           IdealProcessor: 3             
UserTime                  00:00:00.015
KernelTime                00:00:00.125
Win32 Start Address ConsoleApp7_exe!wmainCRTStartup (0x00007ff73beb3c60)
Stack Init ffffd001f8b64c90 Current ffffd001f8b64550
Base ffffd001f8b65000 Limit ffffd001f8b5f000 Call 0000000000000000
Priority 10 BasePriority 8 PriorityDecrement 2 IoPriority 2 PagePriority 5
Child-SP          RetAddr               : Args to Child                                                           : Call Site
ffffd001`f8b64af8 fffff802`e4be9b63     : ffffe001`8917a080 00000000`00000014 ffffffff`ffffd8f0 ffffe001`886c3fe0 : nt!NtDelayExecution
ffffd001`f8b64b00 00007ff8`cf383b6a     : 00007ff8`cc0d3777 00000035`3578e198 00000000`00000001 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd001`f8b64b00)
00000035`3578e0d8 00007ff8`cc0d3777     : 00000035`3578e198 00000000`00000001 00000000`00000000 00000000`00000000 : ntdll!NtDelayExecution+0xa
00000035`3578e0e0 00007ff8`aec355f2     : 00000035`35977a40 00000000`00000001 00000035`00000000 00000000`00000000 : KERNELBASE!SleepEx+0xa7
(Inline Function) --------`--------     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!ClrSleepEx+0xd (Inline Function @ 00007ff8`aec355f2) 
00000035`3578e180 00007ff8`aec354eb     : 06000000`00000001 00007ff8`aec35450 04000000`00000001 00000000`00000000 : coreclr!Thread::UserSleep+0xb2
00000035`3578e1d0 00007ff8`4f1ea095     : 00000035`3578e3c0 00000035`3578e4b8 00000000`00000001 00000000`00000001 : coreclr!ThreadNative::Sleep+0x9b 3: kd> dt nt!_KTRAP_FRAME ffffd001`f8b64b00...+0x030 Rax              : 0x00007ff7`3b770002+0x038 Rcx              : 0x00000035`358d33a0+0x040 Rdx              : 0x00000035`37b5c9b8+0x048 R8               : 0x00000035`37b5c9c8+0x050 R9               : 0x00000035`3578dd70+0x058 R10              : 0x00007ff7`3b780022+0x060 R11              : 0x00000035`3578e170+0x068 GsBase           : 0x00007ff7`3b7ae000+0x068 GsSwap           : 0x00007ff7`3b7ae000...+0x0d0 FaultAddress     : 0x00000035`37b7b000...+0x140 Rbx              : 1+0x148 Rdi              : 0+0x150 Rsi              : 1+0x158 Rbp              : 0x503b1+0x168 Rip              : 0x7ff8cf383b6a [Type: unsigned __int64]+0x180 Rsp              : 0x353578e0d8 [Type: unsigned __int64]...

仔细观察上面的 RIP 和 RSP 值,都能看到它是在 Ring3 上的现场,分别对应着用户态的 ret 和 ntdll!NtDelayExecution,输出如下:


3: kd> uf 0x7ff8cf383b6a
ntdll!NtDelayExecution:
00007ff8`cf383b60 4c8bd1          mov     r10,rcx
00007ff8`cf383b63 b834000000      mov     eax,34h
00007ff8`cf383b68 0f05            syscall
00007ff8`cf383b6a c3              ret3: kd> k# Child-SP          RetAddr               Call Site
00 ffffd001`f8b64af8 fffff802`e4be9b63     nt!NtDelayExecution
01 ffffd001`f8b64b00 00007ff8`cf383b6a     nt!KiSystemServiceCopyEnd+0x13
02 00000035`3578e0d8 00007ff8`cc0d3777     ntdll!NtDelayExecution+0xa
03 00000035`3578e0e0 00007ff8`aec355f2     KERNELBASE!SleepEx+0xa7
04 (Inline Function) --------`--------     coreclr!ClrSleepEx+0xd 
05 00000035`3578e180 00007ff8`aec354eb     coreclr!Thread::UserSleep+0xb2 
06 00000035`3578e1d0 00007ff8`4f1ea095     coreclr!ThreadNative::Sleep+0x9b
07 00000035`3578e320 00000035`3578e3c0     0x00007ff8`4f1ea095

3. 内核态线程上下文切换

上一节的_KTRAP_FRAME结构只是保存了 Ring3 -> Ring0 的现场,其实还有一个现场,很显然是调用线程执行 Sleep(1) 后让自己暂停并出让cpu核,为了让自己下一次得到完美的调度,此次必须要保存现场,那这个保存现场的逻辑在哪里的?其实是通过内核的 nt!KiSwapContext 函数实现的。

本来想在 nt!KiSwapContext 处下个断点,发现命中不了我的 Sleep 函数的 SwapContext,怀疑有cli之类的屏蔽外部中断导致的,这里只能反汇编源码了,参考如下:


3: kd> uf nt!KiSwapContext
nt!KiSwapContext:
fffff802`e4be3f30 4881ec38010000  sub     rsp,138h
fffff802`e4be3f37 488d842400010000 lea     rax,[rsp+100h]
fffff802`e4be3f3f 0f29742430      movaps  xmmword ptr [rsp+30h],xmm6
fffff802`e4be3f44 0f297c2440      movaps  xmmword ptr [rsp+40h],xmm7
fffff802`e4be3f49 440f29442450    movaps  xmmword ptr [rsp+50h],xmm8
fffff802`e4be3f4f 440f294c2460    movaps  xmmword ptr [rsp+60h],xmm9
fffff802`e4be3f55 440f29542470    movaps  xmmword ptr [rsp+70h],xmm10
fffff802`e4be3f5b 440f295880      movaps  xmmword ptr [rax-80h],xmm11
fffff802`e4be3f60 440f296090      movaps  xmmword ptr [rax-70h],xmm12
fffff802`e4be3f65 440f2968a0      movaps  xmmword ptr [rax-60h],xmm13
fffff802`e4be3f6a 440f2970b0      movaps  xmmword ptr [rax-50h],xmm14
fffff802`e4be3f6f 440f2978c0      movaps  xmmword ptr [rax-40h],xmm15
fffff802`e4be3f74 488918          mov     qword ptr [rax],rbx
fffff802`e4be3f77 48897808        mov     qword ptr [rax+8],rdi
fffff802`e4be3f7b 48897010        mov     qword ptr [rax+10h],rsi
fffff802`e4be3f7f 4c896018        mov     qword ptr [rax+18h],r12
fffff802`e4be3f83 4c896820        mov     qword ptr [rax+20h],r13
fffff802`e4be3f87 4c897028        mov     qword ptr [rax+28h],r14
fffff802`e4be3f8b 4c897830        mov     qword ptr [rax+30h],r15
fffff802`e4be3f8f 65488b1c2520000000 mov   rbx,qword ptr gs:[20h]
fffff802`e4be3f98 488bf9          mov     rdi,rcx
fffff802`e4be3f9b 488bf2          mov     rsi,rdx
fffff802`e4be3f9e 418bc8          mov     ecx,r8d
fffff802`e4be3fa1 e8ba020000      call    nt!SwapContext (fffff802`e4be4260)
fffff802`e4be3fa6 488d8c2400010000 lea     rcx,[rsp+100h]
fffff802`e4be3fae 0f28742430      movaps  xmm6,xmmword ptr [rsp+30h]
fffff802`e4be3fb3 0f287c2440      movaps  xmm7,xmmword ptr [rsp+40h]
fffff802`e4be3fb8 440f28442450    movaps  xmm8,xmmword ptr [rsp+50h]
fffff802`e4be3fbe 440f284c2460    movaps  xmm9,xmmword ptr [rsp+60h]
fffff802`e4be3fc4 440f28542470    movaps  xmm10,xmmword ptr [rsp+70h]
fffff802`e4be3fca 440f285980      movaps  xmm11,xmmword ptr [rcx-80h]
fffff802`e4be3fcf 440f286190      movaps  xmm12,xmmword ptr [rcx-70h]
fffff802`e4be3fd4 440f2869a0      movaps  xmm13,xmmword ptr [rcx-60h]
fffff802`e4be3fd9 440f2871b0      movaps  xmm14,xmmword ptr [rcx-50h]
fffff802`e4be3fde 440f2879c0      movaps  xmm15,xmmword ptr [rcx-40h]
fffff802`e4be3fe3 488b19          mov     rbx,qword ptr [rcx]
fffff802`e4be3fe6 488b7908        mov     rdi,qword ptr [rcx+8]
fffff802`e4be3fea 488b7110        mov     rsi,qword ptr [rcx+10h]
fffff802`e4be3fee 4c8b6118        mov     r12,qword ptr [rcx+18h]
fffff802`e4be3ff2 4c8b6920        mov     r13,qword ptr [rcx+20h]
fffff802`e4be3ff6 4c8b7128        mov     r14,qword ptr [rcx+28h]
fffff802`e4be3ffa 4c8b7930        mov     r15,qword ptr [rcx+30h]
fffff802`e4be3ffe 4881c438010000  add     rsp,138h
fffff802`e4be4005 c3              ret1: kd> uf nt!SwapContext
nt!SwapContext:
...
nt!SwapContext+0xc9:
fffff802`1a9df329 0fae5918        stmxcsr dword ptr [rcx+18h]
fffff802`1a9df32d 48896758        mov     qword ptr [rdi+58h],rsp
fffff802`1a9df331 488b6658        mov     rsp,qword ptr [rsi+58h]
fffff802`1a9df335 f6470380        test    byte ptr [rdi+3],80h
fffff802`1a9df339 741c            je      nt!SwapContext+0xf7 (fffff802`1a9df357)  Branch
...

上面有一句非常重要的汇编代码 rsp,qword ptr [rsi+58h],翻译过来就是 esp=newThread.KernelStack,其实就是切换到新线程的内核态栈,并且在执行 nt!SwapContext 之前会进行现场保存,比如上面的 xmm 之类的寄存器,在切换完之后在新线程的同等位置上pop出这些现场。

最后一个问题是这个上下文保存在哪里呢?通过观察是还是在 InitialStack ~ KernelStack 之间,并且比 _KTRAP_FRAME 的位置要低,画个模型图如下:

感兴趣的朋友可以在那些能被 int 3 的 KiSwapContext 处下断点,比较下大小即可,截图如下:

三:总结

哈哈,是不是非常有意思,一个简单的 Sleep(1) 涉及到两块的寄存器上下文,并都保存在内核线程栈的 InitialStack ~ KernelStack 区间,这也算是加深了自己对操作系统的理解,也帮一些朋友解答了一些困惑!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/598353.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一元脱单盲盒小程序源码系统:自带流量主,低成本帮你赚钱 带完整的安装部署教程

“一元脱单盲盒”小程序源码系统是一款基于微信小程序开发的社交应用。它以盲盒的形式&#xff0c;让用户以极低的成本&#xff08;通常为一元&#xff09;尝试与陌生人建立联系。用户支付一元后&#xff0c;系统会随机匹配一位异性用户的信息&#xff08;通常是微信号或联系方…

Linux 内核调试

文章目录 一、方法论 一、方法论 qemu 虚拟机 Linux内核学习 Linux 内核调试 一&#xff1a;概述 Linux 内核调试 二&#xff1a;ubuntu20.04安装qemu Linux 内核调试 三&#xff1a;《QEMU ARM guest support》翻译 Linux 内核调试 四&#xff1a;qemu-system-arm功能选项整…

uView CountTo 数字滚动

该组件一般用于需要滚动数字到某一个值的场景&#xff0c;目标要求是一个递增的值。 注意 如果给组件的父元素设置text-align: center想让数字水平居中&#xff0c;可能是由于元素内容快速变化而导致渲染的问题&#xff0c;在APP上组件可能会有轻微的左右抖动现象&#xff0c…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -后端架构搭建

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

Java接口和抽象类的区别?

Java接口和抽象类的区别&#xff1f; Java接口和抽象类的含义&#xff1a; 接口&#xff08;Interface&#xff09;&#xff1a; 含义&#xff1a; 接口是一种抽象类型&#xff0c;它定义了一组抽象方法&#xff0c;但不能包含具体实现。接口可以包含常量和默认方法&#xff0c…

单目标跟踪算法SiamRPN

目标跟踪算法包括单目标跟踪和多目标跟踪&#xff0c;单目标跟踪在每张图片中只跟踪一个目标。目前单目标跟踪的主要方法分为两大类&#xff0c;基于相关滤波(correlation filter)的跟踪算法, 如CSK&#xff0c; KCF, DCF, SRDCF等&#xff1b;基于深度学习的跟踪算法&#xff…

PPI+机器学习+免疫浸润+实验验证,如此简单也能发4+

今天给同学们分享一篇生信文章“Identification of metabolic biomarkers associated with nonalcoholic fatty liver disease”&#xff0c;这篇文章发表在Lipids Health Dis期刊上&#xff0c;影响因子为4.5。 结果解读&#xff1a; 识别NAFLD患者的MR DEG 主成分分析&…

MFC CRuntimeClass 学习

新建一个控制台工程,按以下步骤; 定义对象Person,继承CObject,在头文件中添加宏:DECLARE_DYNCREATE(Person) 在源文件中添加宏:IMPLEMENT_DYNCREATE(Person, CObject) main.cpp中, 使用宏:CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(Person);运行时类信息 使用运行…

分布式图文详解!

分布式理论 1. 说说CAP原则&#xff1f; CAP原则又称CAP定理&#xff0c;指的是在一个分布式系统中&#xff0c;Consistency&#xff08;一致性&#xff09;、 Availability&#xff08;可用性&#xff09;、Partition tolerance&#xff08;分区容错性&#xff09;这3个基本…

Python基础知识:整理5 序列切片操作

序列是指&#xff1a;内容连续、有序&#xff0c;可使用下标索引的一类数据容器&#xff0c;列表、元组、字符串均可以视为序列 # 序列的切片操作 """语法&#xff1a;序列[起始下标:结束下标:步长]1.起始下标表示从何处开始&#xff0c;可以留空&#xff0c;留…

C# xml序列化和反序列化

问题 有的项目使用webservice返回结果是xml&#xff0c;需要进行xml序列化和反序列化 xml序列化相关特性 C#中&#xff0c;XML序列化相关的特性主要包括&#xff1a; XmlIgnore&#xff1a;这个特性可以用来指示序列化过程忽略一个属性或一个字段。当使用XmlIgnore特性时&a…

Android 13.0 recovery竖屏界面旋转为横屏

1.概述 在13.0系统项目定制化开发中,由于平板固定横屏显示,而如果recovery界面竖屏显示就觉得怪怪的,所以需要recovery页面横屏显示的功能, 所以今天就来解决这个问题 2.实现功能相关分析 Android的Recovery中,利用 bootable\recovery下的minui库作为基础,采用的是直接…

Double 4 VR智能互动系统模拟陪同口译实训教学场景

Double 4 VR智能互动系统模拟陪同口译实训教学场景&#xff0c;以其独特的特点和优势&#xff0c;为口译学生提供了一种全新的教学体验。通过模拟场景和虚拟角色&#xff0c;在真实的语境中进行口译实训&#xff0c;学生可以更加深入地理解口译技巧和实践&#xff0c;提高口译水…

服务注册中心

服务注册中心 注册中心与CAP理论介绍 1.注册中心 服务注册中心是微服务架构中的一个关键组件&#xff0c;它的主要作用是管理服务实例的注册、维护和发现。 是一个中心化的组件来分散的微服务实例的位置和状态。 注册中心有三种角色构成&#xff1a; 服务提供者&#xff1a…

SSM框架注解大全

先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01;❤️ ❤️ ❤️ 文章码字不易&#xff0c;如果喜欢可以关注我哦&#xff01; ​如果本篇内容对你有所启发&#xff0c;欢迎访问我的个人博客了解更多内容&#xff1a;链接地址 SSM框架注解大全 三大框架注解…

Huffman树实现文件压缩

【问题描述】给定一个文件&#xff0c;文件由n个字符组成&#xff0c;但他们出现的频度不相同。要求对该文件中的字符集构造哈夫曼树&#xff0c;并计算编码后的文件长度。 【输入形式】 输入的第1行有1个数字n&#xff0c;表示文件中总的字符个数。接下来1行中有n个数字&…

跨境电商竞品分析:洞察市场,赢得先机的关键策略

在全球化日益加速的今天&#xff0c;跨境电商已经成为了企业拓展市场、提高销售额的重要手段。然而&#xff0c;跨境电商市场的竞争也日趋激烈&#xff0c;如何在众多竞争对手中脱颖而出&#xff0c;成为每个企业都面临的挑战&#xff1b;想要做到这点&#xff0c;了解竞品情况…

Apache Doris (六十一): Spark Doris Connector - (1)-源码编译

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. Spark Doris Connector…

vue3中状态管理库pinia的安装和使用方法介绍及和vuex的区别

Pinia 与 Vuex 一样&#xff0c;是作为 Vue 的“状态存储库”&#xff0c;用来实现 跨页面/组件 形式的数据状态共享。它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话&#xff0c;你可能会认为可以通过一行简单的 export const state reactive({}) 来共享一个全局状…

【Python】开始你的Python之旅(Anaconda、Pycharm、Jupyter)

Python工具准备 下载安装AnacondaPycharmJupyter Notebook 启动使用AnacondaPycharmJupyter Notebook 引言&#xff1a; 信息时代&#xff0c;计算机引领。人工智能&#xff0c;Python是基础。信息时代学习好Python乃是在人工智能时代的立足之本。 本文&#xff1a; 做好Pyth…