一:配景
1. 讲故事
写这篇是起源于训练营里有位朋侪提到了一个题目,在 !t -special 输出中有一个 SuspendEE 字样,这个字样在 coreclr 中怎么弄的?输出如下:- 0:000> !t -special
- ThreadCount: 3
- UnstartedThread: 0
- BackgroundThread: 2
- PendingThread: 0
- DeadThread: 0
- Hosted Runtime: no
- Lock
- DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
- 0 1 4ab0 000001CC44E5C490 2a020 Cooperative 0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA (GC)
- 11 2 19d8 000001CC44E84700 21220 Preemptive 0000000000000000:0000000000000000 000001cc44e520d0 -00001 Ukn (Finalizer)
- 12 3 6668 000001CC44ED4520 2b220 Preemptive 0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA
- OSID Special thread type
- 0 4ab0 SuspendEE
- 10 3b6c DbgHelper
- 11 19d8 Finalizer
复制代码 哈哈,其实我特别能理解,很多人学了高级调试之后好奇心会爆棚,看啥都想探究底层,有一种技术上的重生,这篇我们就好好聊一聊。
二:WinDbg 分析
1. SuspendEE 标记是什么
这个单词全称为 Suspend Engine Execution, 即 冻结执行引擎 ,那冻结执行引擎的入口方法在哪里呢?这个磨练着你对GC运作骨架图的熟悉,在 coreclr 源码中有一个骨架图,简化后如下:- GarbageCollectGeneration()
- {
- SuspendEE();
- garbage_collect();
- RestartEE();
- }
-
- garbage_collect()
- {
- generation_to_condemn();
- gc1();
- }
复制代码 上面的 SuspendEE() 即 SOS 中的 SuspendEE 标记的入口函数,接下来我们深入探究下这个方法。
2. SuspendEE 到底做了什么
假如你细致阅读过 SuspendEE() 方法的源代码,你会发现核心枚举变量是 ThreadType_DynamicSuspendEE,它起到了定乾坤的作用,参考代码如下:- thread_local size_t t_ThreadType;
- void ThreadSuspend::SuspendEE(SUSPEND_REASON reason)
- {
- // set tls flags for compat with SOS
- ClrFlsSetThreadType(ThreadType_DynamicSuspendEE);
- }
- void ClrFlsSetThreadType(TlsThreadTypeFlag flag)
- {
- t_ThreadType |= flag;
- gCurrentThreadInfo.m_EETlsData = (void**)&t_ThreadType - TlsIdx_ThreadType;
- }
- enum PredefinedTlsSlots
- {
- TlsIdx_ThreadType = 11 // bit flags to indicate special thread's type
- };
- enum TlsThreadTypeFlag // flag used for thread type in Tls data
- {
- ThreadType_DynamicSuspendEE = 0x00000020,
- }
复制代码 从上面的代码中可以看到 t_ThreadType 是一个 C++ 级的线程本地存储,意味着每一个线程都有其备份,同时它也是 SuspendEE 标记的核心来源,假如 m_EETlsData 的第 11号 槽位为 0x20 的时候, SuspendEE 标记就会被成功打下,而且可以通过 gCurrentThreadInfo.m_EETlsData 变量去跟踪来源,有了这么多信息之后,接下来就可以代码验证了。
三:案例验证
1. 一段测试代码
代码非常简单,就是一个简单的手工 GC触发。- internal class Program
- {
- static void Main(string[] args)
- {
- Debugger.Break();
- GC.Collect();
- Console.ReadLine();
- }
- }
复制代码 接下来使用 windbg 在入口的 SuspendEE 方法上下断点 bp coreclr!ThreadSuspend::SuspendEE 观察,截图如下:
一旦将 ThreadType_DynamicSuspendEE=0x20 赋值之后,接下来用 windbg 去做个验证。- 0:000> x coreclr!*gCurrentThreadInfo*
- 000001a1`668ee8c0 coreclr!gCurrentThreadInfo = struct ThreadLocalInfo
- 0:000> dx -id 0,0 -r1 (*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0))
- (*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0)) [Type: ThreadLocalInfo]
- [+0x000] m_pThread : 0x1a166902e50 [Type: Thread *]
- [+0x008] m_pAppDomain : 0x1a166948b40 [Type: AppDomain *]
- [+0x010] m_EETlsData : 0x1a1668ee880 [Type: void * *]
- 0:000> dp 0x1a1668ee880
- 000001a1`668ee880 00000000`00000000 00000000`00000000
- 000001a1`668ee890 00000000`00000000 00000000`00000000
- 000001a1`668ee8a0 00000000`00000000 00000000`00000000
- 000001a1`668ee8b0 00000000`00000000 00000000`00000000
- 000001a1`668ee8c0 000001a1`66902e50 000001a1`66948b40
- 000001a1`668ee8d0 000001a1`668ee880 00000000`00000020
复制代码 从上面输出可以看到 000001a1668ee8d0+0x8 地址的内容已经被成功种下,相信这时候 !t -special 也能拿到标记了。- 0:000> !t -special
- Lock
- DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
- 0 1 640c 000001A166902E50 2a020 Preemptive 000001A16B0094A8:000001A16B00A5B8 000001a166948b40 -00001 MTA (GC)
- 11 2 3e50 000001A16692B2D0 21220 Preemptive 0000000000000000:0000000000000000 000001a166948b40 -00001 Ukn (Finalizer)
- 12 3 6a24 000001A16699F8F0 2b220 Preemptive 0000000000000000:0000000000000000 000001a166948b40 -00001 MTA
- OSID Special thread type
- 0 640c SuspendEE
- 10 76b0 DbgHelper
- 11 3e50 Finalizer
复制代码 那这个 0x20 什么时候被拿掉呢? 这个在源码中也能找到相应的答案,继续 go 运行,输出如下:- void ClrFlsClearThreadType(TlsThreadTypeFlag flag)
- {
- t_ThreadType &= ~flag;
- }
- 0:012> dp 0x1a1668ee880
- 000001a1`668ee880 00000000`00000000 00000000`00000000
- 000001a1`668ee890 00000000`00000000 00000000`00000000
- 000001a1`668ee8a0 00000000`00000000 00000000`00000000
- 000001a1`668ee8b0 00000000`00000000 00000000`00000000
- 000001a1`668ee8c0 000001a1`66902e50 000001a1`66948b40
- 000001a1`668ee8d0 000001a1`668ee880 00000000`00000000
复制代码 当然假如你去寻找 sos 的源码实现,也会找到相应的答案。- HRESULT PrintSpecialThreads()
- {
- ...
- if (ThreadType & ThreadType_DynamicSuspendEE)
- {
- type += "SuspendEE ";
- }
- ...
- return Status;
- }
复制代码 四:总结
挖掘这个标记的前世今生转头看其实还是挺有意思的,coreclr 居然新增了 m_EETlsData 字段来给 sos 做妥协,哈哈,这彰显了 sos 一等公民的职位。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |